* [AUH] python3-behave: upgrading to 6 FAILED
@ 2023-07-12 5:30 auh
0 siblings, 0 replies; 4+ messages in thread
From: auh @ 2023-07-12 5:30 UTC (permalink / raw)
To: openembedded-devel
[-- Attachment #1: Type: text/plain, Size: 1247077 bytes --]
Hello,
this email is a notification from the Auto Upgrade Helper
that the automatic attempt to upgrade the recipe *python3-behave* to *6* has Failed(do_compile).
Detailed error information:
do_compile failed
Next steps:
- apply the patch: git am 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch
- check the changes to upstream patches and summarize them in the commit message,
- compile an image that contains the package
- perform some basic sanity tests
- amend the patch and sign it off: git commit -s --reset-author --amend
- send it to the appropriate mailing list
Alternatively, if you believe the recipe should not be upgraded at this time,
you can fill RECIPE_NO_UPDATE_REASON in respective recipe file so that
automatic upgrades would no longer be attempted.
Please review the attached files for further information and build/update failures.
Any problem please file a bug at https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Automated%20Update%20Handler
Regards,
The Upgrade Helper
-- >8 --
From 815d53cd9a095b0b67dc6f98ad9a7fa251edefbf Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Tue, 11 Jul 2023 17:12:25 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..5c49cc00c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From b941f353c129f73934853082f3f3a01cebe6f944 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..2be6811fb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 0e26bbae1f9f8d60c3ab9470b3685af1dde5b6d8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..3e4f6fdf2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 66324f8dc74715a5018d1eced225557c40bd7acd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..ec55efb50
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From 74d539b86ca52e83255183d96b93ff7492751b6f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..2cbf02925
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From db1ead991924fb71d87e02aa43ffa73eae60594e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..bcfe315bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From 24811b631e0eed92347880f1dac3f932f4b46f9d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..b8e804269
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From 19a4134596217540832ed394d790d7b509ec865a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..9ab6cee15
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 29d3ef4d3ff8c836bc592b92687a28bf873d0e0c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..0c70cc566
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From dee5266820aabcfe09d103cf007bb26b9db54849 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..122b7e9f2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From b4a40c4df5872b0c9c7293b3a9fa057e208361d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..726402a8e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From ffbd9840d2c2e273a0ce2ea8fe20afad034bdeb2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..22c6c6bb6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From 430d19123b3a7adc21075b1befda8b550b7eb641 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..aa67bd2f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 5ea1f1b47c14cf9aeabe7d8e22511d54b15e5f1e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..7322216cc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 14e4c88e9bd1a12a2f081dfb2709df9f78106ca6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..04e45f3f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 12d37ec6af46d3edb806679183ab2138e4f1f5bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..cbbcfd541
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From eabcf73f0e7f26ef021cef30950c7bb3d2442226 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..7c6e9b521
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 5e529b9ae17c15231f989c17fe1a09897edf6477 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..4fe596a2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From c046c3a31322c7c29ff6d7f6172df41da3530683 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..121da7094
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From fbe6b2937087db11f96e21b36a540270d7c2b165 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..cf9aff701
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 41525c748413405d0faf6c8fc9a345047e31a1a7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..5fb1176ac
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From fed4bb3273633e5f81fc8ba21c8b62255c7eefcd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..b46846478
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From d54d8c038a7e5f9d2168daa6e13362d1df7d17a5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..56b8bd6c7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From db66eecf4c5301ca45db9f71c7b2c3c26d5927a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..8f9464a2d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From 467b223dc84f52933b2782ce9d116e89d48a26f1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..9213ccd2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 11ea2c45d5b88c92b25ab8e8027a931df81c2abc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..67ea8860e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 72279d87372cf21d980c40d01fce2f59bb734884 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..c41068c96
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From 362e778e5e57054b45099f1432abea181987d3c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..8f0ad3a82
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From d892f81e3bd3e979d3677c39c3a9efc40131b452 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..e2d86ca99
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From 11c4ac862bdce901ef19864532ef6d17a85799cd Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..f050c9c85
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 502382430f1bee8d0a9031fc70541488224a1029 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..6f71729f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From dcbba864209e8784ecaa5a1cf80b346b39b2a5d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..4d44fea08
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From 9c9bb1e6320a6ffbac0b171d9cf98cbb311104eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..1fdda3a7d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 8fe1bc305aeea5d86de1b3c5dfcd2e7984fc5145 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..7a47c7fe7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From dce0e40c9689a8d4fe71c9b2e8e4decd72dcf574 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..33738459d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 65b17ccbea1a17b26e6191a1d7336088d0dd4181 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..7fe451b2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From 2c886d201c19085ab52065d47e4f86a82a77f991 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..3e9a46a0c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 57a9cc0e1c99b0ed2ec5dbfd3fcfce456cfe13c8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..14394603f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From a6388c0e97e4378a4ef628361a46ff6d02d3159e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..2b80d5df1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 93f578698844d7276e5b2f287ee7ff3bcfc049bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..85c1bc16a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 21a399bf6e2e2eb09c98d173f1d0975bcecbf1e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..6d69f4754
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 1d3c288a2eed8caef24356e2a92483425bee0378 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..00db4a627
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From 281bb19a47566c01ce44e7184640728bb56d4de7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..82bf8bb08
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From 8ab2ff875382a92a1a72cf82a346c4acf2ec6859 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..c383e39f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From 64e58e5721f4fccc9cdf49b524271265a0c776e4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..606c1318c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From ad723ba7e7c2204bbd29115e967d249827d3c8f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..05c33089a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 8c02ce2abbab65cfa4d5b0f306bf9e4735a7195d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..ebbaa72ed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From cf7b144c917f5bc067dd8fd2a4d98c859849dbf0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..939a0901e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 084f823fda5939442f48eb34d99c0ed48d675097 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..8d956cdd5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From d3ef06c2dfe44b2286b42b7ddcc66f7c9e6aa245 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..b630d48e7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From b52d7c5d0141971f2e745ec806f829e2c7c897cf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..764ff7869
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 4f013b949f6528ec70105c1fb8e292f23e3a13c8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..964b83e69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 26e431c821c260a981ca8ed28f21330c8ef17af6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..8a07505bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 5917914e415c728e130d828cc1ed4e2199b1fcdc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..aebb68589
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 484af9f2be3868f90ae736c040332dcdc04b5d02 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..101a887e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From f66a9fd1bf7c2ed117fe697b80a0ea4b071773f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..b5bac22cf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From 65ec9d5d69251746a2723f7d2455c23166f3d1a9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..79718261c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From da0a88f5311e4181a9c24a5853f6392209dceabe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..4b708fa94
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 709d7c3604cc8f958dbd6360c66cabf612419c99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..ff0a4238f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From c507804a610e09aaabe7c5da7665857ce7947736 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..b35ba1448
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From ee4784efbbf1cbd18390e2dd36f61dee1b003858 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..b59ebabe1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 2c3c2a4217d033c3f7157046765e0888bde3bfbd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..0b8f63408
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 2034f2d33b24d43c9f0c120ace578ca53656fab6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..ffa265eed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 82065e999bfc125461c2e3db17856bf5a1384443 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..243cb82d0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 1d45845ffb9a30ca66806ba165bea54f7d43cd46 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..683ed9275
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 5c181e07ca0fda2ea26cf8c8949dd8baaa2be953 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..143c178d0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From ba6f76873dadead64be57c215ba2f7eb40d0ea6d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..f0e642634
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 7bd632e11a5ab75cec3e20722cf8fa3cebb1983f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..97db40580
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 56c4ee34b403d69ed3c7092edee95ae6f3b6fd74 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..1cdf70944
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From ff1f1e74f5f81eb634d72d472ce18f8d543bf3c0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..df5b0d372
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 451f88ab5d1ea7da9737ef701a4309963516f3c4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..122a63af8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From 37fd19b56ba31634a34349d956120b6a5898e2d9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..9b0e77d1b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From 1c3eab0fed1d0eb33da64eb568e42971822655df Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..79755daa7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From f9977969e7fabad5644b57f53462954ed097e66e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..444ed5d14
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 70943d9fae75cce204052ac0f498e603f1c7f4c4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..6bdf151fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From f2704149ae1bc11b01b6be854a28f178e20d00e4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..e71056e1f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 95812437eba0154b91108890dd53aab9615f7860 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..d244eea22
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From dcde4bb2e1b32d676bb0ff16de80dfdf7df6b085 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..60e9e9309
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 230b5bf8184beb02a3fa2678def7f11acf916e37 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..bd55ca5f2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From e55569d0a3fd1aa653fd702705e09359bdd90685 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..da7b2be63
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 7b5f6f27a281714c6fef8e609416b7f405e8b1ba Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..3b6b83a1b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 07ad7958eab96a4dadae514f7f963fa73d356191 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..843f53113
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From 2cf10215b5e8fcbb9162bde48456b60786d828cf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..d03a7ccae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From a53e82fa963696988c743aaca2341390332e7e3b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..32304e345
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From fb3f43735f7c9b8363940595d12d7b59544f64c0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..8b0f22d8f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From d65b38953bfe1cc346a2548a8e617229df87058b Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..39a6d76c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From 61f3a341b7accd0580af7c7b2e8db4cb6a7f4bec Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..eeebd7be3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 9271668477574b317c72099eab587f516ae082de Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..33ec83848
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 85d1f009d933d356e65a548f29e4d5a1e78a72d2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..758bd9878
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From a45ef62144036b481e8432bcd2698a5f7bfcddd2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..9c6735886
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From dc91d552ba45dd34022f37a4d4f328d5de5c1e17 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+zYZ)}O%{W))0}wi?6nLIH6>bN0tevk;A;ws+bRx8RH8&l^izRaF3H8Jc(VrOok-E<o
+zc<1%qjobj~Tbh)4fFw{Ro1c;ZGZ<}~$tyQgNKPzrJ;0(v1(2FB>b%GZHv#L>9hl2?
+z_4nO+4`>`PUSZ%GbP-txr*Bk(wvy%%?vEZ1&JbRiC&06ltJV6MII-2wZlr)s5Sz4v
+zs4#($o8l!E5C~EsIc~a;z~@xJ*#`}@-#u_2ra|skSAx}t<!B%^Gm$0?<MoWcd#mP|
+zj9suR_BlBN8m$0Q<$#58a6v3F(m$!ggAzw*X$8Tix4pdt3ayjpeFO`i31;Z*9$_UQ
+zmX7+P8O^d!u%W7@uvKZnwn)H+V#jR>)A1*R1C~9}%Q52{0diFr#uZrQWx#*^{BK^_
+zobCq?3|a~IP=6v_C~BYKo$73M1BjvGXlC}^WAfbdGwWy*hN%NGv*#C>RXoN!&xC(n
+z=5zYowQ4dVv6ZlxUuJ(JoagLEp^39#X&mzKgNx!a>~Ud=O4NXG{D%R`z3O^<j8co<
+zw9fNq-zeCI(cT#kF9y~q4$>f4ef<tCufD4H%t-zc<=QCmc-`v(%5JE$A(OnYJ(6G8
+ze)j7TeEBddgaFFR;2$i1La^0*G(g}szqOcwsWxeVOQyV|_4f>A4I{`(Mjt#IUJ#6;
+zMqxzzQrt<yWNB}#hs$<WuC8wK>`k5tyC$>lnqhBTE8OPhX8&b@vY=up+H9*X@UBeO
+zhSV}>(f95L_`n#bEsy57?uwDu8e+&ogU)K3l4EeThwF55s!*-56;Squ^bGlOH=oS|
+z3xs$NYUrCNe<q)0C#g%JB>Oj$LyJl?s6Kb{v4SF}^hbnxOtvbaNp2aeeU@#>!M~kV
+znSaX;JKlAQ*N%Evksa-`vOPG`yeBdPk-pB8wB}}>Dw?dZ@sh4;bev~X18AZqa;wG1
+zhg8eZW&KkDJcxW+1>^bKkM)6&dDB`zjNXZeQ23XCa*ivkN0nDzLC6*DiAF6reX+Or
+zH3TK}$&xf`u*+uKWw$o^SZk>Ml}2X8o`=MM8N6DP%38z4W7V9BvfZ99EV!VDt0KVr
+zn&oS2OkaM7kvml~T{(VX$4!US=iiBToM>F3LDM>}zf^*e9$^8c;pHQgUp&+zwM)z>
+z^v0Cr*L8$MbG#&e%O5V*a%VeYrn0<@5GSSBvsnL&Dp{~(v&uVI10*^snzsZ>Q?gB#
+zns6WaC*<EZFK2G(4P#G39P3vHAQiRrmx7w51F5f{k{J4lfq)UdQ0_CFx^UO9q1dzr
+zdW9n2HS7?eOU`eb2+b67g+}1-lBdYaF=taAlEvz=I)%=GrWe*SlFu7Wo^pO=s-);&
+zN3;u<fql4#NnaEgQ_9oH$c~hXC4`>>@w~<+^9GZMGNtI&jH0hM<oZVQc3%dcfGn1B
+zClvwA;;&@U=arqjIF^-(3vWjtt-!1&#i($O4%1NmvcN~)Y5z5F1+_&I6^L4fnx_VI
+z*a%nNl57@oeonY!&H{+nEII8%KGG704fp3Wj;tAVx39iUKjrktvs336&pkCG1pcYu
+z%zdcR1oj=pz>L8bKfFBOJ-E@j>F#3_I8JGutif0*s;)8Ml@Zq!>-h!s+<6bXE;9~9
+zfj1AMb3QIHW!7(*1l4@wkSGAfisCJRJZ-FI5Q3-kqLGhugRM;^$7fXyPKcFz0gMT>
+zC;|wNs<z<S$mg-(tmlGBZPikh#O>!WKb4w>!*r|lqvfl;;(s0+4C`7DRnFjS@YI$>
+z-uiYT4bN5F?HiEH{U{v`;sXhf79m(50_k<yl*$kf7uMjR;4%EjmIdO~+H<U>D_*$O
+z(Pu->8gl9W#h@*v$g*{E1~|3*v=ARsI&4ELEFpl7Ps@0GwnrKsoHm4mz0DJD03LrF
+zC;R~X%?F9-$U&O~Mdqb#{QosYwz!8Ve}z|IZHyEM#EA(F_;Wo@#DDtZbJb5XfR2`}
+zTxeF?Y*3pX7F%j>@Ei-#&6!m_!h@#OES+bB1Y7wXL9o4~ZI52}Ub$enr)&W(mRAXp
+zk4;AlGp6o{655fB6Tx4z)OK9UVpQBaHB*$<oen5Z9neo3F8%yaGoXvLz;f8&hWeAw
+z9KZ0$Bw1zGW^IjRN)SevTr$;hg4)5TI|Wc}+a~=r#V(XbWdofxdUpqaha{GDVEe~x
+zgL-PQ<J{Y%>ud4}*6HIGFH1*_p1k6a+$|zp>Gjl<d<firttQ&XRTCh~dgOd5kvjo5
+zCiJyJ2I@N*2tu)MCwSH-;4y$@denpJgtnoO?xcpBmzb}DQZ#Q+*D;i1@#rpWjVx-p
+z%6Dzo`R&frFye(KQr)MNjnv6f74e!&hwY2U47AR1r7+S;;UN9)EEX<v?oLrLP_Gvr
+z5q-NHvBW^MlEPW<FZlW#3gikRCS$eBuvxy{^-wEd70}DYp|rx`9>uu|TqDx}X)DJa
+zF*~h7%g1@W8)S3rXSG+Qbf<TC<4R0yReBcufY)2AyZ}VAw6<d;sfOs8Su60WAH=s8
+z>}Aj&0HM95%Cg{UfzWs1F$oSEUn~S}JG{kPX!kEM8msLXL2cdL^m$^Tv`?1(_3VZB
+zDWLDKt@K`9YiM(LY}Z<S@<Ku=22N0{m(V})*!99cH1c}QSs0N_iNg^fr&WP3W*}Jm
+z0ZIk|NQ#;G0_F&%?a%F8k_XPTvQBVOX$p-_3P4fXG-hQ<yCj@$7-}hewa1#tGRS&J
+z0oi@+3=x?FFoPJGQ<t*v+4R_2s`nmWbebBvkG9%|3jn9asXc*uK)ho_)9QY#g{^H`
+zm#n%f(CwNC{>T*7WYgSZeBB&>!FgaA(nt98Kl$Vw@b)L5iV^OxFcPmD!38HeHMRGz
+zJazMK$#_9+VyvWF*os?Q+D)EUUyO$*LRTU?)4yjUA0qV~?-dJDvXE~&g6!_aPdwF>
+zJFnD;s;3(UIOL&;55bAvjoz70@0O#zJKR$~>Ep#K?le5hF@@MI4G=kHNZ^&i4K5}A
+zs4Jgn#1wQ3sBMb!rY)5y#<)BKh|QeY&eTN)BYM=wJmu_Sbng>uh=MO~{3O{JTwL|!
+z&$a<Z^)jwwi6xd0iYFZF9x5F0(h>mm_ik95*9S=uk1?gS70Ub1^D|OL-e=NMy4~Yc
+z#&Wx1Ehli_b!T9VD)h_5v!&5K<ZO1hw+>!9xY^B@0uf1`@sh^mi*!{;ZcYc-<lIUp
+zvr+f~HOBeootIIIu%YI{AwBB&xIOFM*yvo^d~fJ>e)+`6=4=1FGT#S5i3w`v2v#i=
+zoy14W)uMU<h#=@xU5g{#Dtm}S=x<H!!+dUT-knPyPz`^Ka1%b6FRMpdwdX<<I~o|Y
+ze!w>t4e3Lr&$bi<%!YEsC~jPuevANlW}1p;n3qwqyFD!qW;6sl77o!om{FOzgXSA!
+zNvdnDjL^*t$$K@Fd(is1QSEf`@b|b>!}32%z=ETdt7jt)XN<8INi}{+f~twrx}KI+
+zqnGl;<#qi_iMtHkS`xk;F0#hHEn3f=turG#FRw_YTseifO`Jm{?a7<FvyxSqz$2nn
+z^S6vIOpXKWi{N5WiFfq@a#*7OCtqmYd|R13hmOf`4$!noQNVeRm1>JULqr7tpKX~a
+zhv+{y+mf~n6`~1wOGDlSFl#$lJLbKNQ{ekO4J6DFcmWcUZ{}f3alYh99FKYqXZKNl
+zgeZ4CrqMB!HI&L6&E$=`*F2#wABRGEBIqFRu+!e6d95|S2667%?Y_C0yAF3w`UWx!
+zhOw-vlh3a?wFMROQpS3VN%E;oT7Sfn5$-LC6v-tBx^A}B+x49i(~{TO9lQ7^0XOC%
+zx~#&q=Lmgy#LXzvRX;)BJNfuCS{rzSCalDBBdJqkML;CEyyM^1JX9hHG~4o!qs2Gt
+zY+{eoI_AMaMndR?yOdONkZSr|RBM-LB{Fi5D{ZI&6oj!ly2<>fV+{fCp$CaY1EFAp
+zgg+Z(j+UAW{R;6eOYQjUrTOKKkEO2<X2<LhI6-=D1s0yI-B(^M5r*6){Q;KY6u!21
+zrvsJr=~!%*qU+K1*vN<vkJ>ey3;sinjz}`np3>g?_YIbgS=?sdgL$;4Bgiml2ml%a
+z#iXG(iNEljc5v8ub-E#fUVbiUjc2=Y619YgoET&oisIT2uag3)t;%UGps!zxHM0Pi
+z<<ZeH_(%!g&-+jTcn|l`;Pvp1cjz;Q83gdYw9-{RE*##>b7<H@_||?@O5y8#tjb7Y
+z$eadIq;^q>1{p4HL`?&6=Wip1Gt2=mU4R=34lfFGSaE!Bg!CiUP_4ibCOgk@Gm;7H
+z%$to{8=V_2b)Cz=epdYU2z&1xTykItjns+UQ+x~77|D1QKMdE<mH6*m3(=wk9q?sB
+zOoubK_x;@jA}aqudA>F5>*+JN?8i~WA9(n$Eu3@2s^o&bSt}P;*H<E>45r;9;5U1a
+zUejR-r1d(51c2p@`ALh{0U&WO6GL#=mT3?@cEJatvUmy79+^eT#e{-?(t03i>Y0eV
+z*1Z`27+^<kvo*MwrT@gj05<V(z)nU}ONeVb&S^&c;!=AS9@J-BP$<e<+f+dtzUzgM
+zW2Kl`E(Nu6TovjF7*&)jpf@1iucz>=w!o1JZr-px%Y0%!>{DHpYV684W@o0MaWa4R
+zdNW_T+6J=KaZ36frb&otwlo?3`lKn!j}_~N^&WVp4kf)-2SRWg`02=??`LSCVVB7{
+zBBE}>+aoG%#@Gb4=N*ePh96_Y6ft6uDZpIOpt<}4x1gX`97Z#&KHN`MrWGTQD3lwm
+zX*RCdBQ9V)zQ)M28Sq{)IxUD6zGM2?%1;zW0Cu|moQu7dG?a-bMqSx4B!ELM0@Lvk
+z%rkX6`#Aoy+;0T=ScNumCiM7O((w~iV4}+un6ngzFbUqY`g1^NSfLzl%5|wQ`FAaB
+zck@C_{1T*l#3=tJ40{$|r31p-Apd~1E|+F4nk<;y_;>P*maElurrIs1oA<pcBljv<
+z_E4}C@#X@0#q5-T5Hb@iZ6x%s8-WzH6rg<mBQ)8Eq2Z7!dHrHDgzLBL9ff?>sJo{}
+z5R1Z=M;L@Z*qtWwAII#^m%3kumv>`S(P}DhXakG*WYy+f`A{~>4NE(>dP_Ij8l8Uo
+zr_%<*+^EoBrPJ>?%Z#`vCfR+mu6rF)Uk&l>LNUD`|5^=Vb2wcbNwvSP_wY|-KLJtJ
+z1c~GMX-DTy0yaj$@?oHbBVDJvb~{z#UpHsnLyioEeWbfPUGXvtP3+e!M5t8ZQMSfK
+zzfnnD*VHC^HWLE=UfEaYl-dC={RI&ex3v_G3%bVTd8%}FrDJ<7B{4ev*Prn>zpBb`
+zriQ6r^wUb;+V7F-->-!kx4xO;@}EoP`)y>mQW!fsXGkQS9_TRy4!Tbf=zy&lQc)oI
+zoUO$%)qNyD!?-4)v7VyAo>vjpqGY(IKFp^q!KWc^4|3P3wA-%ZCPLEYS;^b;4KI|~
+zU2sHPd3O@N=~g2EGHaz-d~K3$xW&osCDl%#3|J`0reXDSsd_N$P6vDFX5omJ6VKNg
+zff`Ov?RlyrfhZpXf;-9yk7eYa1d8Qi%{rglVFBo=VXz%ZYF6CTq#G*&RtHZnJ1}#f
+z$AQfZDbM!+oZF578{N*yb(6WrwOBcSfOq6rAYV+cK<8fgR1B0IFgk0`)dEhYKLzYz
+zpBWW{(qWqOq-a$eE&7l(2e;t(-Gj%tdBFE8T&<zX?WD<ZT&;r;qn|j&mWQw)`Wd4E
+zf7=!N`tPi+bJ_SS&!x_RRKnE2nfuXm4v4$Y!VZB+g57hT0|>e%OU`P8-vS%vUd8o}
+z6D!#`SX|aoq+8`WTnKB=PR+Onu@tAuHKZyNq7>Icqw|To<5yTXXz~^dd6z2wWVK2U
+zqZ|*EWa!VtPfgRi^6tW!R)$0IPWht;yEa4ARu%MytYNQyp57%rBqB;D9r<ft?~wrO
+zUpPy8en*y%T!6nCRBFdHF2Q{batfN}$uF3s>^p+HSIUM|AF$yb75jWu#tOdEp>Aus
+z9cgzBpSa{_Io9ayjkj4$Zc%QcP^M;_h>Fd?Z$}Q>h}mf4apQMkb$Pph;QmzZ?S>0C
+zmVD!h0yi8U+)Bv@Z*XSCkCY!y(!NP1DO!*DQH^cI*C>s7v>dikakxlnm&OMv)EDHD
+zI*$#w7v8ogIqIWivESnTDD`<<CE~y+jjht{We(OGJ<k7mlT7T4BkgIIFYsZ0;FLcQ
+z;xqjO8Q@>9<>ThCV-YlN=-&uCGB$Ge5uTkt3-oUq%4H$B)G{Crnlm~GhB{vrMGtB6
+zZ%`kTcXIi+;_$Zx5nYmd1Ex`42Je&{#_-~`HEI=xg)e*3G{2$?>K|3WhGAwlFQ*s@
+zXRoNhIfPdm!)FZKEZ3wQ!nzCgz)02XH$;ES*$|jeUV1U@<~m|?J{*{9b%K!Fcpr1l
+z10zJ!TEMzSr&*5~FE-2=PDSmQ(W|;~u>>er!!sNFBd4_D=O9Vf*wDn^`K3}fBZ?9|
+ztaQF$IHxFGd`>_bQIUnheq2=ckHd)g2Id+`Vmii8(mzOIU5{K6u0MyQ_6Dw4*^)>l
+z<Q%RTA(6=LO+-+Dx7Jt(S-YTs6{JMx(fPC*K&?e9WTLnTv0s<j+BGpUH|^-!r(ngy
+zPS^tD4&k#n4NR?ofUa0p_v_ZT;Zn`7c%g0NO(qHLg9GZM4=Yeu@x)Qs)Eh|E&PQ_e
+zv)vDcn5^{@6l`GFh-pnKsi5%S<smny9Sxe96F0*WT)aNFhgu`AYk;0p+&ULfy;dim
+zd?sf%Oj14v%-1<C;lDYl&NNyMoGtPPHF1@PJk7>}PW-2X7{^Z<E@+0I_Zgs=Ily3d
+zp3g-fyC14d9ncydYo6QrZ!nm{@BjLSmjH$$ep}z?%i~EjsqJ}V7AHDs(w8mJ*QJm0
+zrVi6DO&3#CHlr_%hi>1e8ur)L$kS6+23jpVS|&?2avOUO9QQBh+CDzhC%7CrhtZj+
+zjV7Q|Z`?wiBO<eM`XS@%|J5{p&)%rz{MR)72lBrkWgFSs|3`5^l9HCq1_6rKOHFHp
+zqTUR?MY+;Nx!XM6d-Ii7IoCv(#Km$SBAL4PcdM@zgzUmQAt2m~ZL21nfSR(EMsn-`
+z&zsU6dOX0}!@<f7*ECl&E>7VS^CJStMf14_Kf$H)j3aIS1_8j^k|VIuTCHfJ(dvS8
+z0zgGGvtRJiV=Vs${hFG~mG=aa>J=OIIP$6!EOMe05<PyfF$Sg4UJf(`V_2yVdOs+M
+zzYksRN@A>aYc3j*z?Q8*fo%S-JLXedc)iJ#wOG&^t)xEDPiAj3XCkdp%s%Chy_Hqf
+z8oLE6&*wEpmhC4ys>d`6eKg-(c`_D6+G1EZG>2gYPM?pQjMP-(jV&Z<#g)?yYCIjj
+zuajVTFb^diw07;jt%KHO4PA}(;*H8a)k2^doRo>{YNC7`+=Z;ft78(K7L|uWtPv&x
+z{4%p0+zMXfexp04n8;G2wLL|JjvTD)E7(6x(<0LQdy<kaHnCBh8AI{t5?PU~nVH%e
+z<hQ=0`G++IQqIGHYF~{_9nrP+3N!I%{e<mUQy69^abM5HAlN$Ide_6qreO^6J_!Cj
+zi7+MC(gTAYNquf0k|?6gkCau}d1&j%K~``Vu4mH7N}*z$EL}iX`=R~(KWOVdyZLxD
+z;;f{+#P2};bN=yTYWqg|9TJ_9nHE-#W7JWg2Ras263N5+&AJh*lYG<7=`607rX71*
+zdNMBgXQ^X@vi;4-8fduDRVtotTVqV@p*Tj-UG~&zkFf=wBOOXoEynd{Y6*4&iYzfQ
+zn+VhgN>ysA@-hyJFQY+E>8JheEYR@W&M&9(ae{a9aXu!{urZw@A$NTaH+mZ6GwMUe
+zN!+^1A<p8C;ZhbY5HZlmRg{kLdU)`suhM8Rr%SBfLcU#%-Zp%Pyh_L1eI{;;q!~wc
+z*+&iB$I;V_W;E`!C;lOLCWj_F<3}2D^MRR%_p<l2j906eY30^Cd59+;qI5i*9?93(
+z<-g?S_kRgph}p)@g#WFdsNeee?<W36zb5RC4*!7#=~9}q&ZI}^en3giM<vMf@|E-m
+zUB_`3r}NMhNk$KjLN-9&uuN#V!N}+l>`+Sz1jakC@$R`p*oxgKs7pUeKJ>=~HpUjI
+z626`$u*#2~iQRbA5Wc0%B}ud&qb&*pls6t!EvBtX^<LwlfW!n;gW1SE!2{ocGp)xi
+zlNHDU4RqWe?Fd!}zahSuRG*580u4|PL3brDnjfsvj2D_T{4R9Nj9aZw&~R0P&UUys
+zWI~1=jOnVn-XnF>A6A2F%Wre^W$-emqKSJrowUqBO&KHHPTdk6F%&`?G&ux$hDSb|
+zT%cGcj2i(g>0?z#V$QOZUSGGS7BQ1TEuxT61}s`bP+muT%Z0i%Y`*DnIID1^EzyRK
+z*bPd8fUomW5!C&$BYlxwE14sA<UlRtBF0@yVVocvOceaeAMS>3<+XB*Ab-vFz6R=R
+zkk!2Ne)J`@4}{5ZlesZuu!+QCkaraF)(_m}4C6SZM}JlZt`K$9%-t89vdgIOpICSY
+zHUPP$Q};zLS^F$Nz<U-tx~fvAy^^Tt%diRd<qlv~y{{dhW6V*@Xz3>BvV(${+jWaI
+zuCn&5nF()FU1E`Xazcen+k%>&yhp%tncy|?Kl{N|zVb0k|0?Qu96BhEnq>*r;0p-1
+z4wX4x!PCa>9HwuNkm?B|)%G{uNqvxS&2^eN$08qoy7u1}>-qxw->C#7>W-w%-)IlZ
+zUxn#^x63ST^bG$al&}>i1LseV5cC`9aml~n3V_U3@1$pFHd*M$N=TX5xby(sINB56
+zI$xd2ro&}}2NEX{1_9m*CT9F9QI~k(D~P}}SMj+Lg^;HNm%c)3=vLMkRD~?lAFiLD
+zES(XST3Vi&0B!KZQ)j+8w@LAKszVTFIL!a6`eZJq7@P(Va$rgqJS>?Dj&3KEZGCNF
+z^AODCq(t{kMNJcdflcujbn<KH1nR|SvtWzqqPQ^(b+S6KDcfk*K96yB3u(fR#>~-s
+zsaP>oT?l~jVeXm3lUfbATt0b*PxDGxLH^(WvL+b+%8*|aC-*lE<iBIItZfX9{!?8}
+zDgG};i);;n0IA-CJ_BPd!sKOtF146$pjaKMS)}1-yV(=PnXQ71Y#q39(!(bB%pw4{
+zM6$1$j=nla$F?LP17MP(7DO;rH$7IB@Q=c*V@^NU5p8iUb5gpZVt27`zHLLcSqP~i
+z7<`-26dxBTvh9uAXhnLf&ZK}4CopDlLO3aFeU0nCnb-w%`e3w5km6MOvq89n?;myr
+z_kNw#5I}nrTp~a2kRwFhbjtRj^0^Qh<`4otc{MFXHNXYctZ|A?acJlfg$#&(30^sl
+z0{R+D;EM}rO3NIs(}OAZH^(K>-y2AHEC%X{YHUn9D?(zi3I!pEinV)OXbqbYUP=FF
+z@ybIq)PaT2n9MXNu&{vGLl6lh>jB?6QQ_><2LCI&q2F1)P4zMAfga5?gw49U3iKU#
+z{!bFGt>Xh9F9R~FLL4&vO&*gA%gHgXZWH_U^q=Sa!zb`JAJ{u1%ut=k;=UHNDXTp!
+zrkjOLejYlk(3eKD)K#CM3eMC#um2LL)xwH7(hLUxFl_MO*MqH|{eRY4AK_ToEQ~&T
+z_zcZLk)E~dt;wk@IagRh(&W=_HroavK#`?2ZgV;>JNufR`Sh4*KS!SI)gkA4)R3B5
+z&SY}jr2IO*3aPMROf9g>@`;VHxN~uGiL9u#ss>_)+dI0i`*8E?F%pwc&`;dKcxe_O
+z7`e8O-=89jGG)ry{ky!Z`&hFzf%iTU2_68Aj~*Bxc64)Mz{|qb)Q+O7{@bWJJjYB$
+z*5KH#4iIV(*t1A?6+wCMKn|dkA(-bKxo~zxB4L8T<K}k%Zt3YQGJG=t0!xr}d-yi=
+zdU*bpap%qAx2NdGxvZ=7LT1Jq7LVy0VV5mu|G5H1t{|;D!2ZC%>BZaLxcwELtkyoL
+zhPa3Gv?jU0ObIcp6W55=L8^>{!$m;#7w4dI7B$_7p*dGXARo5bVOHE|ubEl_01tZN
+zAQVXjq0p&DbY}MS^Drg_eS|*fiwHeN3J=mO40GQc=fDiHNTMm;@#x;r@i$0%ah!kx
+zW&{Mbv(dCH#=j!MwhiQ>db!M4=|a5W)QzPKEJ^PD>+$H<b#>ydM<-@RZg|uC{gO&`
+zYDi-(73@QNGtb$Q9uI5@YPSRlF&g6^A_sPt`_t#=THwAOs!Pw0{%7-Ad<<^zk`EK7
+z7tgal!)l9QTw7kfPVCd;<B^Z~Wz#{SZz=%QS~_5Eq~oq$swuga$e1s;j@mqxd7lpA
+zp1YejjjO6w-e>X>T$$(Y{F6R#$ZR_FcqbScgeM7J>4ftkAb`jF=l(s^QR^stk+^&4
+zw{pQr7Ui!&nUQV9NHdoL0FU9t6SWf_g>?>pp{$C%-zIb#kFS2zXSzTVtbP$h@?|Mn
+zb!U}oD=7C62Xg9$XnKL8io?EP*)zhqb{A^DOS_Ma-%!@^X;s9|l9nCIJlMh>xt9NS
+zbM4`-y-BnXNS1l8@u#9^fz`FvlwaZj)-Hos11J-rcUkmcvfqs~nI4WD*(pTVMOXg+
+z#n?GTXBKViHnweB73Ys_R&3iz#kTE=ZQHhO+ct0A`*u!iyY2hB-q)IQeBW5Tn+W|9
+z_*?-@%vZknn3-i4!3K?C-zizB<~A^P46!IC%?etJ#BcADVG2LLHOl)rd>Qh=CxV$R
+zEoE8Y9djluU#+BI;awx`bOjllpZdJt$t!^P_#*0vk*WJm=Ld+;mJxC3gU`_g$;7CL
+z6uQ8K6CxH|WEf_f<_uVeL@@Ti&zeo{O(`Z7g11wUFOkSBo2N5iBo*Uk?(HGq|KG*l
+z$M;7|5XV+9-R~~afcf&5T-VVbh<g<e_a)l?ivj1>uuuyZ{LlM`nI61*16R0PxS2Vy
+zN>q}8!kh!Q=`%7&PJ9ptY<-qIuaKgd1+WTIsMj${I3B;wc~&6%v`qnO=wmVa9;KUO
+zQT18eo9zmYT$C<0of6btF5bMT>{<nA-~P(d({#}{<|&7)N?s>_;_-YC7<~v@x(K@W
+zRuc2aNHkd^I8nyG&T)I3P{3f>h&*)|tH0TkN@eUA0m9?L5psUdB^(wayW?S5aRz90
+zhFybHSqnSR--?X9gegV^ZIV2>cE(#Ug*-`y#oP5#ZRxXwtTmF4E3FJ|eP@f9)An`a
+zC{D>$wT_6FLGmCRHaQ5p1w2wfl1TD;;&i)8knhSH=b!5fjQ`LI^h_zDB~X{xD+M1U
+zBy#?Ic_b$|nBR-D2uRAqN>OI!-Q&Bq&qmarXu}or?<y!+y=X0iYa#|$_Wasx)$Dfa
+zOkwmHs@C(*IBLltR(y(Kx(U4SE}N8!Vzd6ql@_<?UX)xSza#>Xqq++hevMr+sjzU_
+zOZ3Tx$s=M_5(i1SR9k=oHk}~mo+RLmOF+SQ;6Qa+=he!ogjJy7@`O+$@Yu+<N;RVY
+z)F)>dDAE9<D4{(NBP5j3!XrhM$md5HXXV_YbHdM}@+ICU=`5%ZxAMuEKKe}k<B2hL
+zk(r^7gNd##{wx*A_N9;n=Lx0XHZa(mBq*Q;Qx#LyN313Xg6xSois&>IV*0e_os07Y
+zEn+TTNNXyNV^$;5Jt%jTUn&fVsZmM+<$9arnDzUgt>#uoZF99k_y{6sS`ryBl=<ab
+zs26JJ-W@&2q8SD9-McefCGZA}!37O!rui!sahsPa(Yt@cbW?XSTm?e#Q6Zc46FuN~
+zRe)PzSd9wh(g1Cbl0%38298!XZK51uut$mWx8M9|LZc!-=P0ct3J?CY0KfN6yV5k^
+zSzKd}GJR%ynnTBgbg4_r2jgT_BWCat78ktl6p(n6h<`?Yn=w7ya6mYMyKBuGlz#wq
+zB%^Q{w*vYHQNT~D3Nops=*g`F@wdbh#@9LLGK&5KRkeLN$y8)C97oHt@wa(&dn%zB
+z>r3Q<O0~V7RF^so*M#fxhP%b$7rLg@Z>5$3Mextc(ng{?BU%mh)WZr}k*D{eQ6IMC
+z`BIib)l!D~-YjkErHC*rw>rAzL@%6SgXI_39IlR5RL$avbVaI68izXx+Hw(5N$N)h
+zvvUaNo2OMrSWy9;O3=aSN)l+y-e%Iq^Y~)jnN_CQ@Y?2IQV1?E3x}&F@ovo|A;QK|
+z92!Y@+wiP}EVH}x{rn4b0+Cdb4fEd`Tb4wzF=B`@vj=t}(qKiahS(T$BkFOao3?uP
+zk9YPt_G|;bRep<W`YZ5le<3NR8kj&vAiVwFhjS5RWI+|Yd1AX+SyH1ZdCS_1?XuRA
+z$9riE4MtCi{uohKQVf4>m!}baR&cDsM3P5r*Y(`^(?;>8r#2<ju$mi}fv*qlvFcPG
+zVFVm^<MpEL2z!LIxaq)%S6erUp<zo{Z%ZfOnCU-zDgn#d0k~y!Kf+$+FxiE9+i%dB
+zg_T^l(?(Wbpq4C1PHaEfa4bE8F8|1+m45_jR##pl*USHYhN?3-=Ocb0ApR?!afzBh
+z^}It{$gpkM6IZXB!7JR9Bs<P#xfYb2I{VuRgnqfvMV#7BUIE8X?7=G9NZ%vIdbnGJ
+zN59fEi<SjwdC`%wxc~t3aE%T=qd|?<MpXDMH6y*blxMbF?)k#_cYC%`?CvND2Ff#J
+zVp$XZljWyuipepS!P;ultxiFBWKB2!(KFcrMh}M>CAzxec3n)Fw!amsQ_@PDAapxv
+zs41_yU{o{iR6aX{)v-}+OqI1+I2A7%Mm)iiXhMT7+>1+Z%AF5ix8we_xW5ADJgYKX
+zSiZ|u<4ZD=X`@*Xk6BqiA@)!4?BOmO(q~{4ps(SI?BozB!n{d@*hM(v8}*>2x;DL|
+zs&Ap>%7JMi|4guEJ56LmzcRthA%?=jG49tLYr42n1Q1;}Zj!bGup9aT5@UW!kOzlo
+z9go+ec^ih7E`fK9ZGm{90yuBSp|l_2*&O7?8~mA0)q+ZhqJ$pN2==w*6!@4CR*rSz
+z07j~I7NL>Aa=}Xe<@r+^cmfktrAk6lNMl)RF51f0^bHv{b|SAH`^o{?$hpdRGmD<i
+z`8JE0@DOg&BPRhL)_PJ3$ns;Wni<%}zG@@~g%(rWne_VnhoCJE(!TpTGeRFRO1P91
+zQ>jJp7XP^B5Q@e((NdU^w!3dfOLgGU8@+VMl)laP7|3{V{y+aDeroy{^7K_|^p!(v
+zd(2(@D8_CJb{<Bw_AqQ>HE(c)cpYKtx-*MXusd!>d6rG3d0}URRnSz((pJp_)dXxH
+ze8-#HFwEjjub6w0=ZI+CL@LuAhL*a7w6b{bYK$s7I@*rfFZ}DFu9E>hmW<VQbn>Ue
+z^OxTX(=t!W%DSx*O+jJ)(MLlBIEkT7VZ4PC$dcf~_gr*j&q0!8>mg`p0&t0z)Ud4C
+zz58pmT4CpEOc4(Beni_=Ge7j;WLowZmez<{jg+229!}~t!k3spVFZ6qKCZrzL&fLr
+z8O@ww7wr#ge09`qM)G2Np^rO-wKk$a<206c*UgpYO}*3$`0-YB=Qc0DWQ+zD54X>=
+z=7%#|)=@~F7g!T4UK-%QrF~o2$BknBI>7F|A#64wRYCEIeEM4Ba}N`p58}gxbdGQd
+z-X?^nTu!mCC;jC#wi<E!mnCg?_aHiecrSDQ`9d{DY>CP)I|4^;rr5PKern)A;+`@F
+zZt;h=O3j{}N(WRKW{8NeD?A1c{j6h|@NfRCTrO+o@30)OS@y!x$$Y?5Q@0o%tA==q
+zYrR}#m{T<}y_z1_RNYey+$(!{+CRNr0^!>6X?l6|LKiQcKx)U~fOMyL{KT36swKM6
+z66)d^98iH$+A5BH#W=1(5$9I9g4=m^^L$vXA$sCRf9EuL0bD->eCY;xXOJs)-PR3_
+zYkq4iLscJlujQ9<%&(z1i?kE(S!DT`kDCsFX8ff7S#^7u`+UM%u|B|b(nfu(ImHTF
+zJCx=^!-Mq5PS`5oFz9pD1@$$fZH}eYir-o@o;Vz7Y-M5~q+r43d_v_YRDu{701(N>
+zQU&7V_O786VxzoPCbLH#KQ6g{=dTcWz+8AURzCR)5nmSy5T=KJ_V(!<fSElWeSs~<
+z7J$JL#=IY1eS2l0?WOk3wnkqPJhH-No+?@6b?m?${q|cW8^~rVh)l|%*6<?9k=lFb
+zMI#r;nu(KkY0D1fVyCn2*b;9I2jk#1>IM{I-Ubky!M|)jVBS>wTYdZI;5m1H99LeY
+z8QQ%`<IbNYYH}aRu7!A;9thL+jQA)Yc?V>_i@_D~Z};LT!^vU?0L$m#<*m=W%iDo)
+zb|qCvS&^+!a8P$G0IIkOfZRu0o4j<E{^k^Zp;iqabrdQmYc|I1B2Hg(L%Fc*S=PL~
+zN3PKAR~ruV;BH9FZY*_jN$s%jy)4L|!S5jyHl0d4GJDZFYJDlPW6<k$7z;lY+*zeI
+zhZooAtPfl2bu1$9oJOWnw0)mW7X*N5bW;=8j5IT>xfO~2%#68pd}jb2wLh!>Fmy}4
+zU+o*G;)&rE$DJ%=I=G2+g&?pbXS>#thU%DQ+OBRXFVC8zUSO$iRn?GFV#{sge?-&x
+zeXJoYgM0Mc4^>5MG+=iQecnR8cx3^+R^Vp!9jX-hQIVrdEs`{58>irr@XNaNY3!hr
+zxS7Y7y_#{6m==y{zcK%3(vitEN^1BI{i*ryx0U}C4mq0qe}qF#>YjER0F<Ad9K&fr
+zBJ31+%GHf42xotcXnznJ*Sgbvg+b2Xlrwc)4Q7dQhwFtMI7!Juf-|EVFhCs};&dec
+z^er6Ul@fHQp_VCL0*M;k6nSm5asW9E{q0CKQtmU}{HHpN0Ek&Arx%0NDu#Qm{!F*M
+z!q6#PeZHH(w<1wTn1qDA{_7Yl0H%5om7(tFVGf)5CvqU=W**Ok5LSYjf;?4#Bn08s
+zKRi?cqe#LH4BJtRDmyvUE8x(TChk^^8l4e|hk6i0y*pT0lIs2vS}GhvS{p-0dj6B(
+zo^`J`SilZJTgqjIoB^zdg}6;X&Hk=aK}^n~MB^I_oD+CN5f8QuywSwhzWFG>*=7Qb
+zG-J}rN!r0dz_Ta@cV^C_K6kYbzi!(?nrT(9Rp5^Un~FgA^WRlv%h|_YXUjBe=o+d#
+z2`QjzABMC|2*pVf97X&0QXAxrho7Tw!fAJ(x>u=~sA+qiDiPYW<8P9ES;X6d^=3(C
+z4286@)BMbJPs(^O<IFS?Qp9f6K%fbV{&9D_@5j~stULFpG;<J1q3|A|efCXD0oj?7
+z?ev_9o)y7VTwt2vd2khYLrfF(fDrOcFp5q=u;&f7B@ugWoGTHBTJ!{hm|%t5x2MpT
+zbi6Y|yy#2Mab*%x)<3NC$*?iywJGV^(PF1?OPqra=_NEV!eb3OV4U2!W-4$6!>j)`
+zlN4u+;|nlq5yz$_(eU$p`SO<i7vDQ(6e(>qYOYuohuSNzZn2|eCD`?}A(`Zt`9}_&
+zLN}0@{vG3cd3GgoRRgeG5ADh$eQj_unZyQQEU)mseaDgx0Pd_`4DxFB3@dL~`d1dE
+z1Z0Jrl_R69wnFeH8%)1HOs0t+`;mY_q$kcL55-8%mlh<|4@Z0zrSD|!zoC*irBQs8
+zt%YZ`Pg1;o%I>~?_PR21dv3~Fg!>I2Fy)hEJ%XK=YBX@Up&4+2@I$6bRpTz*145Bf
+zbOJ@+;rh)P`xUyu`|dr(gKUuH0-s2Tf+6Z!9^%c2(v&4W5`uOSB3}@q_~XTE#hy@H
+zEr!iB-h1hh36z>vMwapYx3h*^wZIRXbWd*1OJ&Nx-Y3`igs^!;Cp>w6Q8%Luun-2(
+zfewJ0Kh}+EnH#U>5D$%zOu=i?YZ3)XbO*`jTaVd{Z^=ER3eFBU0jmV?4mYW<ppa<k
+zpOJE#uac(=S2g7G{pGT|LO$xYBQA)MR7Q7#Mc-10)zc&xhr)TW+%#G0AmSs@7zkbB
+zJ7Zq1W5QcVc=iXC`x0|8*x;~VW&A}brjuF*;omAH@QSDZe#Bn5h~qkg?kw9x3*0qg
+zs{fhbvQ*(9A0@+Cjm7YZ9-3M2wf)y4_Dpr^@|TYML@=s4dA3I^1mzzN+c){9hz$y5
+zN)UQCaXV+>dDNEsFsun-dd{x2&r!!{77*O`MCTG)f-c?OL=L)JOUcQ+1{pmE4dG}R
+zqZ2jj&U1~~y4G{2o`u7+p;)Y|kQay8B~#4DqXb^`J3sE0EpnRE42HdYX*Qyzyt9M&
+z7?I7JxA({WoQr!$hc}_0FE*@m#9AhVF&>cJCaB53Ovcoo!VO1)v<yuaI8@vzONRu5
+zh2p{}I0lWW6&Fi1Sn9o&r*sArwqiFIQUcmxh#tF_k2MV4yAqO=f;Y{d3gI2zadHBS
+zZg_3YPc6!F5~ROIc4pbWYpjVVNxxQrD+s8pL?@Eirsmij72_AViv%a2JA)?r@KH$%
+z0^icW=GO?waBPH6Oq07pP*KNfy$fTgS&cS7r|by#`T46Dd}4O=5Ya1EsMOmOuME+~
+zTZ<m0xp+nVP#Pk0%&-yL*}iyPy5EqwId*JLijB-V&b!;MBX2YxL@TNqZru&6ZV&7T
+z4|aasUhWnyIBsg5pd#430ET11CiV|EPZoEyKDq}dSMN@3X;tz)+E(`5UBiS&8+xi0
+z&hB06v$0E-PtWXWi%nZ+X^-K#b}l%nh%#)qml`aonc>ES=@mpJpN$f@dl{TMi6<}l
+zRIz!!uUrKl4dEpQAG3emftc9dovs=mp;M%;x0C>kbJ`-aoflE<UAsuDQ(`uWJojWK
+z8lLO<SC%7Hb%8|edDjjbflYGqPjK4^lMsPfy;Z+vIhYqq>M;#P%DV&C0~EoXKyeE&
+zXV$0Jvl7#3N2>=OoznZx^AaM<+ptPDOtkDE>2mN7K`pmY(MA3>@(4XPbU&;nx2iRU
+zH)C;&cEwsxqpTbS7x3OoM;;rrOT0dzq{_TkSqHHh4c!Cgni+EyO@yx=%bvPZ9JYPO
+z??-DQbDC4U=wJ0O!8?9y@46;y+cx;@Eu~b}eC=}!+%^ccYxEIcSWI3v?2kK}i^bYo
+z&$b17_^Wgmh%EzFo((=v){O0lHe?GQ>$<UpKYJ6ALr3xmxorl`Evrs~s3<kAQb<Pf
+zEdL-aSWU|&<Bh+RKv^38%rJ8;ygC+N{ev}$za^m^M}Kqe>>{J3pJzo)m+vXEY@E$A
+zvQUc`{QR#Jn?6EvR~a4%=v?GK<M|v->>W-1{Ob?@YqfKwZDY6Diu?~7Dwu;TabfM`
+zvfdaB3_03_NhI)x-h&e?*ox|&VP!c>bse>>uI1NH7mKiz7jn^2yJSTE31TWbr<W-J
+zZyGLi9{<Sww~G5T!<x#yEb*(}Yw=OQN<zz6CixB~ZO@oHkKwHuhxbOaY=$-=e$~3^
+z6o9YJvCBepOio2>i>{5)M*MxHK2AHuYMUc0E3HAaVeN>%CyS+R!`{KEu@r_lZHDpd
+zqN2sOP_F?N#R60rzmB7-vF$DX7a0=DQMI~SZ{7_?ZiMgWgi}>ZG94(GNZlcA%87~2
+zIFCGdjp`q38hr_d!|MIi9X2)&n~L(b7Ouw1*H9xCGb($B?14gvxqOc3Ya*6#lj<aW
+z+2#^O-Z%M7JGV#|hpT5y6%DA<Z9&56e)Ig18|B}l{pq9(JW&p!WARkvQmN6qHqe#d
+z(@J<8AN4G4N6eH5z+s;dm*S_?^fU%jVID|W>G01mG6bIL8l>JSv_U)^pdWqMzHyzp
+zC>|KQ5kFaf$6B;)H#FhlPj#qSN<SToj<vU!WhefmbM1O6AC#YYxRbvUygQy%nT>29
+zC4*E7qSV7;5LJn%ZsK!U8k+VdM|$&O`GR~xgM#6~h@&;ai9%0s+Y9M6dDYp7zQs38
+zQ$?mM$n=e|zj(R#WjE^8QBE$!wzO0EEx0ms5wf8BX1ymnunVeMvzyy8_<D_x^?<;9
+zSG@Pxuh=A>JaeV%DkDdaZ7&L8JO%UwQmPps+@^(-BG=|G^W^YWNf9ft%rlWLcsaXl
+zWeoQQcPOrkF%fk+64hKyu^2qG4t@Ajg$iP)N71*?J}@@Ji9?z(;+Y>KX9s?{F5K@^
+zmY4yA=>hADCyCf|B|}Fl3Sr{4{Amsnq!{-vk@+<1H@n0tf<9WQuwEoQpkWLqu?M6%
+zC@mxSJNh4yd{}4`MZ7C$B(s~VJDD2Zwr&D#3I63Ms6#2_ugJf`4CJW-VVcd-ri#Sa
+z1W6|1fJe^r47M-BP+z0jQeCH*pFGT}RUsf=v9{o<0II@$B98g4%36sTz9MFUOu$p{
+z=B9aQ!f{&{U8Jz1ll&M<7)h9uR!rGSE;w+dI{RFcK3|1%>o&sKk2~T;xOL$|i_;BG
+zgTC92VPVZF?aiFH{NS^dn<7maJp{b7e&D7)KEHJ~?G0k+Zq}>L`lvLdx>dWiWc9Jq
+z|ByiEz}8MYO?j{s7g0H{$3GMOnUFE9EKL<zcQupZ5U#nN+;s-d-XSKIb^WUpdwjae
+z6#@?5r4`a!XXC`n^<_MbZ3jj{l8#t6*=Ypt39m_Yp|w%%yl^o2UWN0?Z?e?;S&bB&
+zWV1fQD;S9sZl=|a3-nkMH9D^mMuRF#TUBlQkn8m4N%{<#J&^!qRr(x^y*atolO1od
+zYUY%YBy1y;?CO*~XnW+kpDASBP)wtI>w~(vn@uo~=Y#(qRQHNV-E0;#nZ_A-nwF$k
+zSFpNbaHxLvgnAQ7jpj+tY~`A#3GKVP$r=(_KR3=Mr)_3Uc0u`{j!TlDG_GL{VBO2T
+zOf*@j+6iB*RAvKGeFLJ@QE6d!GBY!st+%*QKEI^$VV9vG4q!^ON#o4@Sn{_$$`V_<
+z4jFMJsz+qsJ%_2Inizb7h!8a=AyP3!m`8-|j=zKie}#<sDVh(Ta3vaTWz=f(c<wNE
+z^NK_}1<IqtE;R%mgBy?6M?T=_;gbl;sApK$VaA%PlDeNEn|7o!rjZ5wweRKP>hi{U
+z5FvM=JON_a6i@VAQL(}F^+Ua+o6v^%o=~Ws?Gq`dZiOH+%zud9#T&0<e(tLTqQ*{w
+z1i`B)o~h&`oT%4*y8F)jIlqk3yt$F8<q4AW48Z2@eoU8>Eomy`-9IaGhmwu+I&YNc
+zSM7)tyiY9Oav7VhXMs7}dT@7P$7PAEWU$=u<Jy$QXYza#1NJin+ttA9XCX9P412nd
+zi&?@kQ75<U0=om-Y&?|NMo*AaoJwy1-{SFN6z0d`g;g!R9Xh{y7sCXGTlI#f{H_Ay
+zLAYq3S`i&$7o`;)UbT|pat`af9cFsIKaw^<I{Ex;>5hsbU(Ixz;$Ms#9k<bu&=uS#
+zyCIsHE0gn2r%Kga*8LPpk)4Vd%C|_3JM?g?p9`qDU52O5KyUH_@KNL<p1K*(+;D0o
+zBS!2^Aw&`h*OGBd6MX<fa~hEG7#Oy6Ta-`AZ}c*wB;cqD&=aK}Vu(A@54g=$HA;~h
+zgs1?QK_aA+?qYv?^0jV-?;Nx4N&XTWdfbiSUPB{K&Yh1~X)pXqD9d6WO+*^5u$Z^6
+z39%r2)#30pEK6uK2b3muY}yhz?sbbhoccvi@45ql{BZ4UC2bKqRBTeF*HJzPt#{za
+zF@E3Wp!PU=p#YAvWwQ1`a2%&`vjw)+xXBP7HP#GkLMm7XJzZ>m6=ioR>BH0!4au(K
+zsPZF*>v>YLX6r-o^_OXZUeI8r3h0r=6cVWA!8Q(<K;#uaf%4gFT5{`~6`2b>vWZOY
+zf!lXR_Nvk77gTY0iVBa5ho{yaK1fR>+o^QaRmhL?{>Jz1{5Dpxeyk&&v1<b>PB>}P
+zYsx!j_{W4@D8m)BOKOL{m401dFB%^VW6=bHrFcezeRDLvh}Ks56@jy55DV&~r>Ch`
+zOWD8qo6tS<WwJ*vAJ;&!)j{AJ>m>{iIAY`;ij^o5=pyC?*)N?HBuNS!IZOf*IFv2)
+z|2-cF6oJE;7w!F>f*e?4Nr8MX$c{`-Xo`=F5mq)pIXKXT3mWRg)I3=*>qjTXepSly
+ziA+v5iyslmZ&(-44w7^3^cz>fvq585rDqY>3=_e(ED&EEa^rFrOWpTNnv-r^A%J~O
+z{+&^2HhqO8N@J+yDNSW?O+B+1fleg)VK^dD$+D)%T^^1l+TglNO_*wg7<FCGP>a<n
+z3>`6%t+Z0RKwsGv=EMI`GUF5snvA;Kpe-!@#o%)_kZ1%53L3=h{&!Fb4%wCa^wemX
+z^-_OWAOKLYB3+8k=bTdTkG8q>=iIpZ4doPVr5%g=N<Vf!dNdoVAcIGbW5Na;(%(Jx
+zh}0tc=R{K|GxCu7J1{u!_A6%pI}GHmw@ywXKh2MB8g3-`yaKR?lc=PCre0o<tIM$1
+zgBN4-l4-LSBm_xMcZPmdHTs86ScCB&vtQcpYhte)E3BSC<IqlZ`D_a0A1)m@f)idb
+z6u)^JX3VzsxyxK&+NC4TYs}UK1IN6s-^vSMQe093#Rgh;<2HfKmm5f{{3TTrh`1Xb
+zU&rqcbj6D|nM8Gd#Poy?tn|y6$zjF1g1q8Qd^^A^8_)}U;bh5ZDw!4YI}QBOkPUi^
+zZbE1uOklkEQ(Y6`LIhEqQ9E@5=gcX(X+Q?-LT@G*mCO9Qc>m!2I;Bj>g}O--EU!H9
+z-^Z_9bd-)to(<SCGv9N(S=4=Itr(mXLgf~vPJel1WWAjYXti+V(Ru1-X~-=IogTMZ
+z2xltJsbr8$Sm5tC1Pe~14Qw^46%}F^oRJ3jb$Ir=5Ef=TC5RHUC{&lJDkUG}X9lyt
+z!S-Uq&DQ8DBy=N<Cm@>i38O%$=P*D1gQLegx#U_4<0s27hF-Ac=4{Hi#;}?e;KUV0
+z&FXrT<DW6y1Fcq7!8WGUP}%NvNM$8b|4oi<$x}-74G^5u<TV&emP@v6Di$W+0OLlf
+zT;Go+tx2R1?RuoG2Xd9j#micB`}aQSB9I(?Fd#JJaqzwVRrB=`uB(1E4{n>L@T9rY
+zqAz5?@?WWR^QOI7eK7mA85!c#R5d1BCVDy%(+YZq&t^jY_{X$EE$T$Iv@mt&nsbP(
+zk88FOHi3wZ23NZoEj>8cgsI?SD~8zH%yGo^YlNkB0*~@1A{<^%fT+MzVKeMywCrH#
+zgJH6_kGGd(?}WlVp^k%X3yNz`nNu5`?FtZxSyY%^(Qpx^&}&m#);8}Kg=HSV46g>W
+z44D+_@Qi)0!|qH%&sw)NdlFlO7ikp;#Ed8wQrr~s0j7Vq#DK>$Ny+!jH^}ETqvIR<
+zmtMM#`f#5iH4XmtJMgd*ub_D5hzG;z92?+L#r@6rc*%?EDokYY&V#~%;l!mWlDhiw
+z?y-woo6VvkuJi=(Ebmt4^k(X4cQf!d!0Pcl^8^~a9{|)j@G^E))}a2Kop`6AYX5FK
+z;rs?EAfD6yfOy$pX!se4yVRJoDL5*mE9~;={YaBeyif<};7!U@Sa}QHXlV&D2!%E)
+zOc?TQaWmJh!!s9-y|cF)>v#)c7L<0Tz{fjXm}Bo6_vboihH2Q=iPUK6fj7Jqc`h7Y
+zAnTSOIijLSlD^8=GAe#4MT0xum;S&`DBC6!n0?V*$C7t^G1hOMfI_(Rw6Om{dqflT
+z>rMrFFB!#S%w1zBi0rP-Y_H@w&3D`(H~A#rJp|uHi(=Tl5}r5q?Cs2GXH4=yHAh79
+zscwOU#QX&!X52G!`L$Id;#CEIni$~IKu;^t$`AXjb^pW^M?(W95UQt!(1Wl8x!smZ
+z<(Bnv?Z5cb5L-~-`@eQQ!8nQyy`X@A;7I?o<Kf@1W@qj4Ux&3SO$((1G1Py<8q=Y=
+zq`q=(TN!!M&R}7wVONGuGY#&+s1Q!zb~3pr4rD3S+trz^4$HEHV}E<^X`0XbnURMF
+znOMw0NZvuM5<AEDiRmZ&%>FH6Hja_$;z;D2dGfH;@B*uxMy=ewa_!f?CfWy&Rdw`u
+zK~k)k?x`tug+eivb$3o@XCdi@@!)P^Z=7Xe<xZ5{_Y5y^f%$6e!oyg{1ntQcV&cRY
+z?uq3+D&sa*e;2R2(@GwGNz1{BxcH1bb39J@;Nyww!W!ZNANF?#&4c6p`_s^62|;{Q
+z1{OF==e6IQ!7g~er1U%X=+~PX3z5Un*0XH;#RK}EOd=4Ckx92!ny&yto+eu2Q2y3z
+zBBG?6UtA&N)Oz5E_pO?-7aumo#h9i>5EwDUrY)bu*bORirb7G3l|<tK*!aabbYWPz
+zt1$<V0&^B*cY&_$S&M&Gp7#&u8jW!VqrkB<hLF{k&{}%+JC^`v;LsOa#KJhWENt8d
+zAYR9|LIxhLMz;kf9g_W|JFMhp8sNNDW@B8V7i^fWb#|5`>XuGPtnnm)D<hY}NN*+P
+z*=QN^edqx>wI<SD;XWV{Jn+jrD7NM-F3uxshfm-S`?9AExCqq_1zOh|DBH~YFF_(e
+z4Cfd;(P^!J#-6Oy+*~)!uB&iGb{&D!0GYoz{aB!BqlxWAWQ=MR{?Rjd!fgG;lEC@o
+z{eeKc7;}{;g<~=Wy=ex*Wl@CcG}2ko)ZGY&ku-Do(I}Vehw~ohLFQ%h;=Y=_F52=k
+zrgx7W;mkf?;A44ovwDKeLB+XD;zXpKc=i_j!IS}DXri%bBp5YPMF7qG7VA%4zbJ!E
+zS-jBAXtGZJP5Xg6j78>0MRx=|Am@heJ&P$2r*~`j>kw=_)L0ri6oPw4s6jzX4GXHV
+zYFOGxnwVO(v*G}u#7BL?*8(MV>WRSD!u`thEl@R5q&^b<PCzi3-2UhcqkAth8a0IF
+z+LsQHn9x4dC?Zr%5V%X5L9z`EFo?Jha$%(3-;8tY@yYQ=7u-;Ev>VV-XJtX4XjSmp
+zWlVWz?A0dwW{lVv?hWvU^wD!ifAqRcy;4HRQV)NJxV$R{ESnVJkx95fxbIoKMmm*3
+zG+1}#=$6$pYR_!~(ay&PO!Jg}dTIj8+1Suo+}^i2A#T;Bn6{eVSq@$Ki}1|dOWR(K
+z$bu<gKEIKruP84cE>WM|oh_q7?$Al?%Uia1PXXhdI1n_%lT*O5vXYzotNs1PSRjcs
+z=SX<RKR}tvah`Mb&4Ri!xLU(7$#qeU`j?ux+H^5Ni<4lSHg>rx^io+%u|&AxZdb$L
+z{9R?rbb3(z3pB|%`W5(pf}4^k8AEZm0Z&2Tm!78+oX<ONn9hz9K<-PbyXE;Ru39=)
+zUGrG;?G@4v?Rm1+;~7%bS<}h64B_Z=F6Sxv?LjtVKQ`&-`$0_Q=BI%ynd($};NY>i
+zEiRSJe>8z7Y2;MrQW<*n%~dXJ)#K-!`p3P?U_;mk18X^C8Lw@JCpQG7k)SC_W|Ym6
+z5U!$OY{FP4TeJs;C~V#toIO>pw{91<6(rA%b!j?hP&M5hmd>~H%GzS3|16vvc{oW=
+zBgSH`i>8T~M*y?}C>#=8QH`z^OLlJ#Z|RrJX^+4@x@-0L9XBeTl<mSaQdH|bnTk7P
+zp-xofT#ouX=bEsb2V6yr8QY{n_1T+oD`SUUSLr)C+8zvq$tiTs6Xq?H8oJi5EQ{a7
+z)5o?_$Q%r>2fMJ;6PIR+^O@l%%eIbxyNXA({+S1U&~cfuH<~{`H(_8$vubvwu~@D=
+z*Fvw~YCglvazV}a5VX{VNX{>^#0=2ks`83p@l5H($ZZRj_%Pelv(qUNr>82=bJu!z
+zo!eUa`!oc9#mu%%=2y44aT<B-LJ6rw3Eo%iePvHY^{w++_ji$PsyyBSuX!r5?aIah
+zmrNxSL#xH3JB6hwGmn+JqJ(M*70|S-EUSDl-fwlJzslFFj)@2=cXwBtXS8;?oIz6T
+zKIt|Q3=Q?@=Md_>*V9iP(c1;T-HUhW7wEn<+*|b910}-B=*Lh!s^RQ+<XZ9{MVShh
+z`Vgu$|1)R(#q`z<$2=F0tcoFRhSs8ha_G1aXA^Tz=N$;)mTyePC*T%#PiK|V+Qu&u
+z(VmL<7834U$zK|eZu6$5SaKde^<NlcNqQ*MH*w{W&EIU|Rmv}1kg$nNDG?D@Ll<Oz
+zHq#Vra0eU@mxu9YZ=Ro7-BSgCoW2=ao9AN?%ilhtdUo5g59rklZ|--zZC>0<(nG($
+zVSG&8J2j7LZeKn(-zR(Lft-Bma9mo|g46F_`O?EHmfSzb{n)Q1k8Ao@(LtYQ5D_uV
+zUSCdcJHUN7BfT%y8DeiCav1)JeE_x7kEW_9klfO7h8naIB#|}a70ZHC`H;Ko5OqYF
+zN0_hN+$+D5IpHHWsdCyHyx9M0bP)r109-!bL#cY+UsEkRYw|H5ba@?vyQdJ9yby@b
+z>YiZ2-DiX#_Tq1!>M*8++n@-Jh-!%g4?gPHb?Bv8S#QRA)$Se>++E?_UUwj?5_HG7
+z2f=l2`RnGW2usM%E!&<{=`*JnTSrAca0}c>r9bd$9AY9nEbl(`^d#!S;^)<gd4-k5
+z#gERVyq6NV(L9NT(@Cgb$AlKz;i7zID<CvuyKdo)j&J}`CjYb90+=R!m!I^Nql#Ho
+zQ!jVREREZR)CWSQgR;Eysn-S7{v5>wFR7YfH3zKWmsg5vA4|vy7rfxu#k04%mb{fw
+zk}%Q~>v^*2+vvSu{cK!=1z*AvLZFKf&ufTgCCStM1r$rkdP}%NC7K8h^I?MT$T5~c
+zU2B|=kT0@}yULZ<u1^i0t{RVAxc==1@B455|JfensD)#<kO2XuO#Nrz;U6OdTigG7
+zr@7*_`UgC$-g%(KIw?-j)QE1VS;if?0LZPBxZ7Mdn%QMqEuh%c<w+zL8hUPQ?)U&t
+z2Btw0Iv$dBoZJTz9>PI@>N8@&LHxOpvu8wDU>y&1&z8kFD1{oo)Jeh+biqhkpaM)J
+zkadJDJmhw!iw_;smpZ&!+MTSu_w7wr44m(Doy6|tjC|BQyCA;!u3wbw(cwVc3>YKB
+z-ws4>t;{@U6P{bX2baE{o<(jEt{^^t-+A1;U1u|p>+)~7zd8QAAuenEz8hP><nMSo
+zt<ssI8!xX2z(W_fTH;p$ewvB(e0?a%DyezdeVJbP$_nhh-k*Nh>gjP)`SNXj5Pq8J
+zyPjUae{;Z^Ok|V`75uGB{)*n+6VWfpIBnWMMIT8d7ycT-abO*gIU1;uBHl+vGXS6#
+zQ@B~ab4Vi_8R=8KEvA%y3Gsjb^J}J|^W}NDs(~j19Qhk_u)#egwXcEE7gP4T?ctId
+z9fh3i!X&=NZVQLf;5Qha=1w_awG+LUCDgGUuqdBTSWw~P@-EHFV1k@0hz4GQ9Hyk8
+z5WlPA1b&|)ZV<jju$AwacuEK0i&<#p;k(BC?Z}SihF=o7W|4##$n)-ud@^SYWt=XO
+zIzSYzlzRq{#$X{jheJw|-48?(!vmU)__4xJuFZ>k|F_pC+pqR+<0Hhn3;WH?4>h@)
+z_&V?zcG^$*X)@PAl9R(wnP_EY^>Is#koc;H94<$!AGI6F;7qytE3g$RH+l%}U0rTK
+zaY8*!P6BFgCK4bSVDZWaBZM+W7d#y4kR&H`s0c2Xl4HDhNH#rDu}c#8?RSqQUvS60
+zh)dNHtIJaWF|6^<H!iJ6xez88vGA4;M`3^s@+V-$B8r4x$ZMc*G{ByjI^d)xXJ6bJ
+zM!~?X6$?F*Gs51WtX4x=u0gVIT$*CC;8cfyyPac}@D_eau0Wb--GeijAlMzi@OM=C
+z>EV>F`;t-%7u4>nu>8^jpUMgO7**#BVN@4zw?e@@UH=j=@jxfHSB1x!f4!4;E*eDS
+z2aYPrP&5OJXWL+-1VM={d5xOuJSd1y0%15S@FdD5R}Uia>~n#P)5X#C+ujjkkXJ7M
+z<u{784fu<k-#9T<`~n$nk5R4P6S<SAaq}cC<cr&e{6YSps)25R26RU9dUzo_7;l=X
+zi9LF<AZz3Vb9xkyaJRtFYf9kOU#NszNg07=rpWQTNA);ZpJ4YjLVJN3HXVR2CNdEP
+zasY>WGayINlk()Zy9~K}A(qD-FN5%_$pXk5qwXOmm82q%1$GKkloZM4sEUGuHLd#2
+zE|G3XaEa|+)urN931jTQ0$61rkCf24)^h0}d7TEfBF`fvGR(K|x8NV_0PZdlnx|&Z
+zFk>Es3A&SX(;H?o0Rh|P%VMUK<&~A_A4w36J6VHhER$yF$hcJIZaat`aI@nm@;)#^
+z^}ZbNg)&5aQmA;pwrjW(tF%05t+bnlT|*c`eYb2uj=U|-M?TT6DL)li_&Vdidc;k6
+zECOzp2ATd;Xb*5u;8ImB8o~{r!#RfO^e9Asz!p%6Xt2Vz<Yp4Xis^9Xa9^%};;C^1
+z|8^KyBGKduA_wKKNb#I#AQyPqWK8A+t96E|C!%`j_;W^2AFvMDN_vwYyBN;{v<g}O
+z*i$nc6PrT@n!G;t`Jr5<TADGkYH<f%=MmuMqXZHT5d5pJLDsg^ZE&ru_hX;R)7S7$
+z2D(dtR#}y>K+GiF7))D9_~Z{7u*B58AdFRm6T#?!PN_g43#}=-*ouWSzhc0D#l&N3
+z^%-<CK7PjjW`*R09bx?gnn@DUH!Z#n<%F~=ZG<HPp8@?)fowtXI&F3zAP-IHD~e`-
+z<d^I}pK974LXPj(5L3bg0cQi5IZ{|$a6EWQD$`g5Z3zuAG>F&nbu=~1wWTumNdh75
+z2ltc5`Wk9}ikR#ltHV(ka!5;RkjN4|L?{Y8OrqS|<K0R=b(|WxKt@1&5Uep~Y#1&n
+ziZiYkTcQC6YS>F%7dfO**(gZ7h#H5)Nd&p>{z<SsM^YnnYluT?g*3$)K`}DQJ*Pis
+zW^>vdCb>XW1PcseozFr$z=5+5;`xBf=dR3fuRKcc8+%VovzJiT%;gs=xC9Kj;IL?>
+z-r4rzLRA63Y3WQq;X0`;WScuQl4el5n_OzZtdEtMKb0{S&KNGx1zK!K4ngj#BNBPK
+z@F4U&U8uMuYct|>c~o-aXC>#|fNtIRh#a!l>HAN$t4+`|a<+``gm8|5jb>`D<OxkX
+z7=kYT+~P10$G<>?7KqKZXfa3+GY}&%5eV9=3sphliJ3|7s63yd8!!j5K3bB+<0atp
+zG5!qMN@WjF+!2>GOZ`Zg-J}tT^n^K%Vgx-zQF>RH)s%RYDpjQs)idx2FpVk06>5h!
+z`cV;)uD)y>HBa*>86_VbY5K#S5$bL{dAQk+?A^~9kuor9rVbA(Y!;s>kHtvG6Fxk9
+zkWY|rw^S=(;LpQ}G4wuZWw84`ufAx>T^9iEv~5UhS#ZynhDfd#vo1aN8g*_59=Sc&
+zrQ4;*tZE80u>z+_&6zHDLuv==Is~KYlvMm%XM0)HcA>JgCos{vQ-BzaJrL91853AT
+zlAc|wu6Dd77bNJOgt=Ti$b17EJ`^?d7eUXeay8Z!gb*lAaWWWF9QJtTPb8(MeD<S^
+z=k&s>1^Y$f#9`k%m&_eFR><ryF#@bn6#|#TKYx-~zIG#S4DmVOZ_A7x9*ve4p60|_
+zM7g0P6od&FL~tYA6V9acF-H}MtXM<9+N83j561LKm}ly;@5ORE*jk>$mMh-GlueIL
+z{RtcC`kAZe1YydBv(^C7VJ(`St>bEyB#6pNn)-*c#gqz(vYNl0@DSDINH88pG{Bl0
+zFx$AZ%#MEFLXGJbU1iHvrC9asV!|{N1<XYqKG46hxl(0a)b4Drq;B~7ai_wqUw)Z$
+zFn+rU`{Y8aTzUri*N-i^;0}P0I2-$L6Y#ulU8VYAQzf<g&zgfP-%Dj<{<_zMi&6V^
+zB}KSe1M4Wl&g#2p#UTZy8ZDSWFW1r3u)H4QXVL^Zyq8aPL<7WuWkL!lP3x<WabdD9
+z)&HoCADWbO#$CV6TcU5q5bs-n`bp&;^AnAL{Zn@Zh>RfNF`HQ9iHaQE)YML-74a%@
+ziIHjWwt<=|o}2B($Z2Y@_7ACNmJIA@yJaG-tW-v5c$DgQ0NM<0Cr$IMmt4G|A8&ZN
+z@d(Vz1*&$N<6a~@J=znT_%^w6EOnUDzM@hnF;G!R>Mk0lTE$L{WR$F)6ve0G^d#3?
+zpNggri=?L0g}d3BL9U{_hgywfNJ;=E6;CxG2X>H2jH=P~md2m(s}O;0Gxs_mISG6m
+z_79q&!?1<2OO98!hTq27=xI^kGP2TJ?S(0lzmyq*cl!$}-l}%NEqJ@VUi}LawNZGC
+zEfuvR+72;uzxrT&fekn2YO=mUxqh=Y?n#2BDsiq*K#hH)9MnN(hG}x}LVFv#;3K6u
+zm<0l12$2*%z>pw%j6{46|GvHQ(IZqR#z6KBMzELD`9-i?GxmUlM|KdwMLX<jYz*XG
+zqT~3`n!{ySA5gF^ng56ffi|fJx6B$bQ(`hZA_zdh%tZvoLoyTV0P>nbpwBu!Jf$uH
+z<gP&ob8sdH8ldk-<cn~NwX~@33fmus5%c+JqjS{7sEC3M3Fp_Xs6+-QHFOVCZ}7R^
+zX3?avAyJ&$yB_b(tfw6UtE8vN=Sw5;B#|-#R(KZt;q^hZ&B@K!>ib`|{f}ZQgcttI
+zH2Xza06)H|bAA}5N08f3hz7w6`{%DSqik@SMLJau!pnq$aUsl`m~SsvA=d2)?g95a
+zO~Q)=Ynt1@UgTyh!_#AAAJl<Vko&KL?d$^sWVsh`>{I$Hr49#HSbv7x^IBvSV~7Gn
+zh5edK9P3VK%R|RR-Bh58dO^|p^TY1zO+69B;GShe3E9GcEfDKpTG60rW$v-Q6aM&>
+zd1$ZGbj*s49%#=<PJMiJiCI{oIDYo-kU0_xcH_G(Dxu4%HsqY(&nM4gt4djp4&|8u
+zRuGM(&$lDinsAok7t1mjshJ_M*vpQ^FFOqrco|3W4cZ*gKUVG4v$hOf*Gexl2+yBh
+zWKXY)o|=KnG%UDvQl<U8*|}-E#$Lj;W82rQ*q<4xc>F^DQBio}S>+h#LErnU{E=&_
+zQ$a|W$+Dor%VaW8tC0!Ii3Ssi9@n+|Px~pv`wB#<qcUOWusQV$We${-9W2_y=8zGl
+zloZBW594w*e;>mNrst~nf^g8e0i$(H?Q(oah%UL2t*hQ5n%G&3*U=g^S~p_EdXXbn
+z4xN24hR>C#8y1o}V>&sYKRp*yzTB-?3M@wcMF3&qw9#<8bBFmeB2qoEjqRVwxCv`}
+zxxdaBYk?WC{8AiVnPfCqNa$td18YKSdL&edxdfvG*hH4ZkGl#2)XMH?X77;>=+mRL
+z<7kAo`~x_zcRrz|j0Qb$zPk__m_5%!+aNwl7KD|?3bI)aNN&Ydo#tPqMV69?(r|2b
+z4k*v*lKk{a0~ZLRm^@d9W2kC73{5w;26OkJI^8Je=`KxIAiteXYVnJ&xBiux)E}Q^
+z%dlTn9RV|jw&e{D-cDts5XT0$vRP{$h2eOhJM3gijaY?TJOI6{r6)OF6t*Q%$kYN8
+z`5L(l#nGgRNzYh?jOT3o^97pF)IHa#($Q^Tql^hu?W5M7c%+KSZYsv$1g+Z+%ohOU
+zylXhXh>3(qje#STzt0=F(3-|YJjhJ9j+v@p1FR){u#;AY_vAn&MjvDAU0f1>t%3)>
+zK|ly_V&SNw-+yaPe&SR3sq<2ap7pr3E~SUmhL+<!EssefE`4Pn*nBNKNM~;f4wV&C
+zx5Q0T4T485WR7z7_U>{mPz)+q=6MN*K;wTMN*6CCXfX%t6LQzwM_lsEwJIqu9#oCG
+zP^&^@V^nkV=6-w<5-XzpXNHj9z48Z}BO1-VdcA0Tlo2qu8ZFzPTQi52hLq2u9T#a>
+zo-_G7N@qXTGT5XBincr=j`yb{gyfdsi$Q9ewlXy~PVDz{CaZ|MODv90l(MgBp6zl*
+zbFIDw!)(*|LU@CEH$3(Dl&tzw`jGmm@=S<G9JVcA4Hmnl)bi+oxn!PhOVE3p`>fdb
+zy;dE@*Q{Vtm*pBK%!S0gxaG+V=?F>6kdYCGRmdJ2Yd>W?!4wUb-zVprzpy=G8SUh`
+zEzcgX7=sdl!wE=Lp#_^{Ofh3Tllw5qI_{eR-ISkkEBe1UVWw>Cj5^vFZl(Em@PN&;
+z_rbzklQF*)oxa><bUA7i%<sm%<#S-x4#u0(DzBqhfsQTLLLXyS9VgFy_^&5xx)x6h
+z)_Q`INUPVy>Y1of6nyA@;e*`NH7p2?UCx#vd?&Y25HOPX<5!+ZSe^#F<Wm*V7gNzh
+zDAuAi3y111kmY;{yuf#9MKZdYzLQ0=dH9w>n!C$vFEN#X%`?(4v{(vE;;;f{U&tKc
+zj?y%B`qUhuhQ-y+`js5zI>1GyX4gEk-`QuKr*?z4vghGh1Kd5~3*W`=H<PBNa-_NT
+z3aeYxNV0_Z2@6-oB$er)3`5J2MXX(h?yJ|L>S=;jxN|;!2^QgOi&68k26mnAHQ`b^
+zw8x3^O<wze=a0<g-5KguFl+K4P+YpnC<nK`vX($QRnT&+&a`4N0O?7ytIlw|tVFEL
+zDeokY>za<&;V8JxPdKMuft@&sF=p#*+DT*R?_M{6-k=bhz&B7TwHS4{$;$ehp2Ign
+z%9}~bz&&zfMhH+mXN@aqPwo;SACE$QL>!Mz5cFmu2(wTTd27jfO2&+EFleiMomGm?
+zdH6Sg<sN7Zix4pv)<L0bEbmCT#@le@Bt4Ov&m*A3hmLpXV1a##mY0aCNl_Llx0c2o
+zDTHIXb+UUTlY}!Y47?I0E!iT`lm#p&rZ}wlOgxP=Kb4+h%rrwXpGjkx4tYv%g>`O<
+z!6;kdym7`N_cLg}D=M4>;klIp23%-d7LyxnMkF`mW#i(cK&*OipXK?+OW^$Z`p}{P
+z|FZG6p&blalm{&@O+hjN{R;ZEObFhaRl`eR{Zy#O5-QNFT)Q0a`tufA0;I}|j#nGW
+zc`$dtNbY56ui9g!+a5APSkimkyLIWK>%_xx$e`+Im&a)@ZOEKffJ~dUXnFd9WV+)N
+zU-|bBtn&pH9Gbx@zWeF?>`^Jt)N;L=(fwZO3A0ePsus+2x;TSdnTJcn!`S}e27HCt
+zauR-Q-Q%rRgeO9u_Hj6>Cjq@pHhiZ6%w01K^6&KX)?n0gSaeR}kGOw#xPeC=Lydal
+zZ<5M>VV851P$mVmj2@h8u$kM!7=f!n2*>$&_j|e`J{gI-AXBL9tc3mp;@^Ut8RuI+
+zU};N+toxzKjN{Nd=V|B;G{6hey5VAOvx$7zG!MzP;4fTcmUpH#w?x)p0oE`<Okm`v
+zFCb|UG4t6Os>K+nDgqSSp92B}Vd8FVl<`iNj71dwI7De_7GwaL*!zOV)B6s7eERF1
+zjvxH5&+e1EX_wP({^8h>PHP%l;0o1s_kb5puATZRH5+aTjjOl6ws^pztQ?wijZ6j|
+zU~qwiFW+k%_3y|8U01q+ly#!Fw7sU=J+i>7E4(Yv4FL!%oC+3MM@~4)34|Laz^m7+
+z42t|JsoZkbs^fe-fqzZUxJ)7pPX-Gn99TIVe<ye$xTlQd$)Ubs_$n-qBJw^U3EUub
+zf5G^AMf2Mp#O~aU2!8^bB7bW)SZxih_zn<KT3^JOv8r<BP)QbE%ky?$M)D0)$Y|~b
+z!Fn6ta_ER+>5if{k&si0S)D|ZDXyi#TR^=B6fToR8R>Fx_?>BHx`Ov&8fbFiwnDwL
+zqMY%%$IeRzHCPNjC~!-b&=~~YoI+S&G(25R?Paw58&4mWR$6?0-M}i_KUz?0xPm1c
+z?MCgmZ?i!~cuP}1=(1ozYPi>+hD!flO>O%Xksy7G*z&s7|J7*bXp<RoK>aJEQaAu7
+z*T?E+Mxn@}_&2+5vq=TKCNKgoShUa$H=6}eeT9ZKl0ItB*+rw$PjUa?M|AKPld**2
+z-OtK-J{M;m1RFxeG`*j1!a6dIU6Z`Hzfjphb1*TCeAH!zYB940+C`vlc8;R|w`Uhi
+zK$<i^CFdzN<KF{84fyXp%6qdfZFjAqzqz-pK%h=S;<FBD7$gOj$otTXS-U(F6oyX2
+zrn;L0w~Y!PDEgdH1>uzb+Pl#>*zcR|1LupeWs8rM0Id5nLq=9x)ly45DEfTV;RPE=
+zjQidj$WZQBf_7-~&S$T^Ow~AXRe|vPXzMKo(KLXf#%}8%_U+)Lw7ub=WlpKcj2i(A
+zckY>?u~EuFk|s$2oa8}x!~8IpWC2tF>jXs_hFd?v@a=*~sxg7)@m4NHcsfPug$()#
+zg-w3=fY!Tu5p;Og<$e_e!A4#2?`>>^{TRrg4IHHoeA(5Q(zth#BNv(RDY<l_NSAfL
+zS^8zpC^x|s&3<u*Vb;#7K#UlVkqY)j!D%V0ywkG{VOAf)y}%J^U1fUIBBRei?MoH>
+zx;|raEx`a3x<mfr&)U@z8Wnn-;D#VJN_Yc#Pm%TKt^g_~d%)&f9(ph{j~+b)`wzEr
+zzZtqwK}a9p^te$ut4QZpvdfB*im-J{Ev$ctzw1qrTxSuTWNMD>P5M0pOWiIy?0K?@
+zU<8VaFPJxGmuJ^*9|nexAId$ihE?Qt%-wU((Z;OXM-4&$-Fm`!xfXwOE6JUcBW4Q`
+ztxBSs4SjbzUe}89{~_$1qC^Rrh0E^Kwr$(CZQHhOTc>T?wr$(C?KyYWJj{3J-udgH
+zR=rfcR8~f1WW?TjOl?OhHdY^5K50{>`xeS0MiY8~sxq*CmYLE;9jFyowl}7`1Eofw
+zv?^QZu@^rmQK+Hvn|M~KFoY<X2FtB+`M=~F6M)n;lk{N4de=4P=B~^Xmd?x6UnG;6
+zC*TNBM=BaA7>-_4XED44Vfi~8RW<kGqh+fx`OrFHjnA&(ahonj(Tgexk5HIW(F^@#
+zrA^psK7W_;^Xhmmm*G7`h^i#c2~t09sLf78yBto`weP8FkHK%#AEza@)8$ldH&2aO
+z5QZr`#<HS0XGCErSFKEa5inD~;AL3r$@f$c@Kzi09E2wSLcp9@=;5;^)m=H9-Ht_$
+z-RMb;iF$Ud=gh0BO&+(z#j>$m>xgtj5^Q?#lgsXsTcLV<&3nH)n)b`h)OQRYqU8^F
+zwB^~_M{Mg@^0uB6y8+L+AME_nsv`4B(F%U`uuIjc(-*kRbs`oCiG`DDD31|G5k{f*
+z>LZaz`_oiqMIb|BmUqtCtI_69v8Kyzau#h|l$7W1AF=7I+6C4^WIcrg+pVj4sA1|{
+zbI=hH^WIo!8v~kZV`54yfiGe9t26nhm8rpi3fbLLx{!oCNi~e&8li;{P_~4+!n(e`
+z-U}C}W49)f)kBJuCwr<6rF!de;&=sXH#eVj%sLAkn9e3qyW#)z8kI>{74>ibDTUY{
+z`TX0Bmb=7sw0*n1gWdD(|2g_QZhW(srOH68LK{Z1)k~eF*{}886FPhWlet>tkE`yz
+zG>K~jHHc98sXEa!oNhNRm_qWUV5B6eOnr*H6H+g2slqKxbmD7XK3Ek8$ApIGIW=6|
+zO_;$OgW<+=%8l3`SxmTssa~|^zgpyMwVR{%By4c8E0VtwF(XRJ8;CSn3v_4EwmH1n
+z(b!ss+fa~2=fVWZOVbF++o2Iw<nI=e8s!%A%U>OoDHh__*@TPcjp^!F%f`(&__uZw
+zyytv4C&RcIO=Rv?b5Y2h))hQ84I6XDmHaH56v_x89Kb%FV?sq&#yEyb4ZwwiD$Sc)
+zr|9;m`XZx2W)bNYxCXQiO-bE~J=OUmC2?IkYq2P+i7AC<HD5b<vhL1v;=*#-27svv
+z@3QeTywmBMn1*PE2}OWhu#KLcA!B-yipm)8mh}Gk``a|AF)rB?-dcS<gaf!xRfO2%
+zJW@kpXmEdgS_(q{OuQwvs-n|1IzsRdzzbECJCF>MoQjUxx9F?B&tQ}%S4V;Iu5FK|
+zYa-{hM=@i`(bC122MVw`UZx>z5@n5!>u9WQ$94A~W#|G0=Liet&p&2~ZEcvNz)ln=
+z3`GEHLTqXq=+@ymDvKH)8sb=9K$D-o7pvAw6uzQGwx0mGBPLOUjhJu~k|Dr&uYH14
+zMmT@AA#_XU>Kzk8O_H=>BvMrgVx4I{T`_a%8bXr8{Vl!r%Aqcgaa4h$PrKYoK~rsb
+zg`_DxuCPj3U1)}nyMBQ|d6h>I6f-45(>`K&+EdWD@6q~4lW3j8J5JZWO_Q`bFDju5
+z13v1mfXX+#@Jd8#<7sKqUWb+EO8uoVxTCA?03vCHpfjkr72I)aS|3J%K>V8El}q1W
+z?W|YrC)%*{py-@I_>?gBSR77eftqaPzW|0b17#pP%{{|~clC8JPA8I70to|ziGao{
+zcd_K__wpE4Od|EsCxi*o)-v@dg-y=@&p8Yt2CUgWNTL>d{MpeVgTGtKtvH~Rn);1d
+zplg$DuB8^%@{^~anR%P4bi_e?8g0j$bt7JS?!~zW^Nrl8D)(vAu~Mv>$Fy+TS{;7j
+z{Js`*e;=~lG^WLaHeV0Rxjdn<=6Rw@b7ZC1Y%yJ)2KJPykE5Z4Dwwhb15TW&U5viH
+zdbT=Stu@i@0#IwEX%aTUw*uPOMAkA1FI=>>-<(m*&4g8VFGY(1^!OO>Ef|3?Ma_ju
+zd4sHsB^b(t`Ck*zZxO-{LRmSU5m?OTci6qYen~&fc7=Oes(cbY&zf(d;?@K1+q)_G
+z!s<6}_Gp}o#c2O-U|ekS-)Ad5RE^*ac&N&6%UZm#SPw?`F{DaehDH0jtWVO?=%u@G
+zB(1Qosq`9EO0X7T@ss16L#yYzQ<H)HrG&fqf|(?*!@R%1*O`=>emM24SzZj?(NvTl
+zeE*2%S~YC*Dd3)*f?6!91-ty|7?W4K7-IO%`^6x3QC{eJ<j#_Rk%3jpX4XSW^#eX6
+zhI~AV3?P(E<1nrx?}?z-B5r3fBVrN;O--;-duTT<kA)eOOj?kqoH|FQ1QHci%opk#
+z_DD;_DNy1I%#WugC1f6x5HP+nt<o?gSYT+rH9d1IS#NzZ4W$(3u1+tpDs}0}s|`g(
+znwm&ZeF7N?WmsbFGr+~q%E$u$VeM>cwfwq7HOzipw;k)RcdP4sbi93A-kB`r>h$`r
+zomN}`+U4RLblhf%-ng6kCp2^j5<R`0$TrjNVq`}`_iZ#!@H0=gVH3l*c$x4WK?
+zyRkQWRi8B$-lOHP(UCB5zcqRRkdHy1##1pX#BG+x=LByb<1XKte(zVZ!@>HWI->!<
+z3f{RQ6K~IPJ_=1AdiM6ugZrH60jUbtTp`W5O1=lR+Vf>MQ2AZs0TbORD%^3sd=-Mw
+zo=du}mlintn=|wDD+!lRzPvIYKo1o$PCJ<W#SWgO6XQNy$!0pHSnyttS~Cr7ETHdV
+zn8%PYh_&!bpG{>@?GImJP)@PPcO81rHA6ggM}~*?cg!pMfswGOdB<{CO$_{Wd`_Vd
+z?vCb3T^XHiw!?F+)^S*KCOf<^nT&zBcw9|c58(q&_PrpYoQCOmE!hb7Z_u>*JFihu
+z`{Vj8kET!eTM2hgP!W|wE+x6vJx|0WmqCA>fz`^K2|akgXpRaZj{x|!LN|uP*JEJk
+zeQIfC;})c&#c~{PVw^D0YHK*5#Hawir0(Ju<}LcQHzye$C5xha_l#Erbs4Ve4p=-U
+zSNm~lf@!W()SVd89Bt;-jHo}yoMKm{@VzVqd~?Ojo#M$?D(%UZg`*iEgS!+)2pjq|
+z-hxv3FL`BFFiY0+pQfGw3ZeyQZ0M;y$1s`a#4MAMQK*TMQW41wV>!($$gDj_|JtI2
+zqZC>;<(;n@D)u*<`+;;`XSEKXHUfb%jB>Z;O$Lk()D8hH(Y-?lZy(dn-C3x2mSqr2
+z7K0hc;EFRUAKWQgBS4BA3(^3EPc#g(m6ub(dMHbgrUjM0a8C4afZ5Ulo=HU+d2h^J
+zapYu#Iu5c&xo)ANE$W`A)xH^#URl2Cq~xST9~4v!B*@4?^QGzeBlPMEv%?y4vf&rG
+zdCW_q9<}|;5Wn|()s>Ivx0BfL?Ov_D>0vK*kf(zXNk7~ulNJ+85*!^0iI&8n&fo0E
+zc_M$dBU;&}J@XRT5<a_~-z`P0VbM~uMWcUEj??$)MZHN(LL_8*v|Z0*RR8f4R+TPi
+zrENjjb`ijEw*kVrMENMUnsFR_z{FOIylyNKIzoY|wB#bkMmFUhfn^MmXe1QNxdfNA
+zQu$dscAHL#aGUg1gHQx;Y;BGHYUD=!$vu`m9g|YDNINV6r5CfVLRONz;2RgdFmerw
+z_k)(KSu&tKUpG*#G+*--byOD%mxbz-bs|{(yUJ*pzk1W9ru1$ixatjcOiwfUe$u)-
+zQ%j#YOFlW<+a10;ylZGUxJ^C2@Qr+{w3FPCF^gnXgF`u5$HqC4j6Trv;tQm^pxLhZ
+zLO@mDaP}1YnR6mpp5eAED1ZGAfOlJj3yiLOTGLR8F(gwl+=_6SpqEIJ{h}+M>&o?N
+zJQrX$P&+{SS}Yk3<<gH=7SkBQF5;Q7-xU5HY~Y=)=AEW=MdvO=(By&{)d_g)<Bh)0
+zHz38Eu++v;slN}<-n{|xf!4^~TMlBk?e8}5^<%Tb!EF2$^}$wh`9@?IXlzHbycd%{
+zr)=vOzv^)fd@(tKyuTQtS4D<5&i3rh{kwFyf7qL@1=7c~U8jf!#dS)()Ruj>{~`GW
+zG2^}-t+=lA#Q0P>wqSS$n9sByJ}iHWE+Yb3Ikb$MuMr*S^Y-ZD!e^pg5vA2x(YM(H
+zbd@dLen~Q%r^Rkdo4zNi)5}w?cJa`#tqqCcov|=hSEK#9=Le+50-|O(PB-Z5txEi-
+z1Ib0Vw!}MrwS`ENao0dJP^A(&dUCUfYBfZLRF$D`XSMca9=I#~uQATW%$th=&U7Ok
+zVc3FR@Al5urkd97*Xo_S^QT`g@5M8i-Of%m*Jjh%dTzx}4J&?Os40xhUu=J9?5OVc
+zf?II`G`!PiC-L_I6;<EJ_2TfXa}g;WvUtkt>E{!;_@B(lL~}Zz>iijvu8sZJ*9&%q
+zcd5`FFEELq7;6yQt=HZ1`aty<taMrI@;eZdEj<Zo(cDG-b#&+hYn0=p*K6|VPeOn~
+z3+@K_D#ljZ1~nxtQ8Rd5#lUhMfa<#{X>$jZH7kW5hhV-43ZbtiPA+&Eoh~<P{9E-~
+z#?%Iow8mT~a6`>KZ?(Ht0shO^oVVPXkD%YHKRnNVG67B10HGeNsPGEeDuDOBrE{+V
+z8fRXO@bQGm|bA6?OOg*SCEh#VMc@m^}4(+2)hIcf75$%|9a>}7f#ErU36Hthvf
+z<Zk9!;nx`$IuxvQH6!WnLVmHbM(FX1O<=7zV+Qqd#S%*<-yZ6Hg7?1w|5*;Do6Q2B
+z0R#Xb1rGo~@V}^?|6wI946H3Y|Bn?>-KzbzOaJ`Nw`#)5%cSrsB3qQlb%7$-8-Z)O
+z@+u;v=`^5r4QsL%0$*H4qY~Dm>n&2t+JD!W4(~JBW^;}?45()dsXMZ2{gnpJ?Dig+
+z8-7x<R=s^$GOInhBly2XoExj#qkE`lWS^VTI|C#mZ68dy)R#%rjT!?H2ngxCx5Ol6
+zO%Zstm@zj;o>y<>#hd0uqaIuFcztK+Qp|E?(a@x}M3_pC_wO?ucQBLcVjr?pl}|jV
+z{i_$jD+*{WT(YxQQHnMyy!Kot%>!Bl5oxu84B#skptk|gnrfzUiB|>vGNzUbQ#dVw
+zkG3t-G+#039amIJX8K^krp8%EckAoNQLm>~*Mw~VPnRdNk#QbOc2XFke!g5}#8td2
+zJ<LI^$R1reODSiK+<Nqz0a$AKur5^W@)w#L?ux8NLm;o64wU7v`a&n%NjT!CB@J4Y
+zqoY4F;x7IYAENu{-a;VF%>p?!+N+%gL!#Gzh``~iNwmh&AySq2^Blm3Vo~2RD|70?
+z<>(sVJ>fRls!STlfT6K4Ma1Z(o2az7tVjHS*g;dlZm`k@q|>r`aoJ}ZLWS|#Ej~wf
+z&qj?QF`4w;>F6+H3D2IiBKTViDr%=+9!;u&6Pd(P5=)eAHlq(=E<$vpQ!kVyOH{{m
+zwJatYj+)Bveg)@f=6iy!b7ddIV@8-CQy-#s5(TDrDdVac^t``gZdT!RiN-n*_7`0S
+z@U0~16Vo*5dMeLBN+_b}w5#4V3U~xNxpi(gs+RT!$85AZvUd<vL0FTBV;rBobloH?
+zz?vC%in#0~48XMw2C36@s}Y{%a8D_UH75<z3CCj*xOPtztabx}t=kJ>|DrmfJHm-)
+z&Q2rK$zXGliwH}$H7^J_5pt4p6r~wA?y4z8_Q>aNjx1Lazy4i_+iFyT`WeHffxP<6
+z3M#{(RJpQ!@a04cM&Ru)LL}BDV?bK^_XE)=&&^(f+Pgrm>QQoijE|8BE+vdfiie^w
+zD#-#65bo}>@({NFloRWGHucXW{N2tQ<-mW*y@H`}%oLHYwNZOXjAe`IF^a#a`oZKO
+z9=BV_l&=GZcAGOLwi$VQz!T6266MewyJq1WC{eBPP1UN8@goGHD(!kR>XSAaSxp8z
+z(TWtt-`fVk{)Yu-V(HojkL353P-`Dg!|3o`nq=p3>|vsu2s*U_hb-;XoZmtx$ZQup
+zEj!iuz=AO+?!SNZ0-q^)^#35<F#kW=Zf83y6WjkYEO^rLkREyalM0w*4U`Q}o|LJn
+zsS#sCqiBj0IDd_;22)Ftq@4u%@wPB5xB9qfPOv!~dDm&C`}6{#2qXb<`w^!u+zyvO
+z<zB@9X-q$g{+|*hnh0W;RX`CT-(F&fR-liuvtr&9eXeUTPb7{Wn8g^zBBeX)&r89~
+zx)PZrL`ONY__XHro_~urK9kakGQ*5{MWqeJbe`5w8>4!nMvwe6d<d%*daW4Z6bTw-
+zWM}wTC1M(5`XF$~qnNBl6Qr3V0}??-zPJ<_IhYCah3UDR!&ga??zB#iOBhlfWq*81
+ztSMv5Ml)Nd0{SHi5s7}5v&pc4{mIMy#cTDZ<Y#54o9VY%$(>zh(4Qd@&_5WryMy@l
+zRaTTwJ&R`Oj$TVu0_J~D?b9j{pQIxpWw|Iq^M<FHq19n<>L0<fYep#KY$j7eFWj^W
+zwL^7tFK*eWxn`EjxlR-z@Q<BUEf{QO3f=4-MlqbPUHrhnVAUs>CLFOo=At%$+K^HA
+zQiNuex+`$tI9*ikoLhrSB`5ox2U$TYChgZ&GfhHl$9-$1I7LdG=M>T@kYai6?i%;#
+z$TZYp$V-tw-I&LPeJ<;fk>31he$cSJ^YCcS;@S&&*M=`?Dfg4P@s>CsWHirA1R|bp
+zpcxc?Wjsffo+IcMNTl%E?!4;uYi$p5jykMz2~Oe7%>c7ZGg#bmCa1lQZp9-rm=
+z&bYYuCn+!Dv-u96TU0avdBFPp{wwzU8*~2-@em1#|NG+qIU)a#v4@FHMHvDBNL=kh
+zO;=q;-NhXW01)`)ALSYt`QLs28OhiUe%0arVWGlE{<m%=COSPm3tJ0kJ-z=on|@90
+z|MTE$&276)R(QWxJppGp2gq6e`c^>D6&oD+DxIieIw1w{CgD+S@_C~2q8+{7P9l+r
+zk2|rHB6L;0Aw&<iqwlxQ!-N@YBj@py0d$)*YPSvc7jM?bYT4cJiI!=B7gc53In8sy
+zwE&P}4+3euma<8mR+ki$)&ceMN@^o2*Zb{<;i#x|rq5e5XkJ~4-ABb{&b0&+w*?y}
+zlGQb_4m@cZ(*q}3*Vt~-b+xmNSEYwudy`>n)G0}xOccXG>zT@S7tuS%{+)O@s$Z9L
+z8Xm8g=$=~sc!!tK$yU3xRI@J1wVAVm8IbjIW0z^}zmvjj`i+o4!S}S~bRV&~oz=>O
+z;dGDHV|I)UY`xYdca5onC^1{j_AyCd3aZ-WvCH^s;+>1C{>UCZhoHK$4&OYIxOX@-
+z`IkAgaHpK>To<14oTJa$sZX9AkG52OZS^xj@@HMN2I`~<NITFHZUzF`+E_&TR~7vJ
+z(QEx!XAWQp06$O;a!t|p&E*~RdywP_2KGBp(a^@T20@Ax$S?S)Vh-;78}8t-btnJK
+zh|l8~kG{7p{_(oM6ppWR+7xYvr$%>4!V+74{E_anaq>sr?$o>CRfCD8BqO&2mM&Cj
+zHA^|w?aA$U{r8u+&N!k&$z6f%VJ24`iyC*O8v9-xf9D+cqRERhKff&yNLxN+)C~K5
+zOCU{-dr=(%47gU4r`jP68z}iXy0*p#Y`&e8{r8`BRD)oRQ}9cTNg7z7U4>wHo^<=O
+zx{S_2A&oe2?jK=$cbKtio2IbfdWcy5G=WUy;b>@(xhEKcB=sCupu#hl`)?TUT2J21
+z^#n><c!-$(lP=BAHKLxZH^qyWeX0WPw9(U4-BTD|_F_7p-x5a5As-yk^mq7RkAq^B
+z`9hcxmxFOn1NmM|rZ6B;;Ey#^-~(*z(Tn%Vh)tmmh}RtL`d$`Sd`vntqM)LP_d9}d
+z+<TN7(;6(=Z?*XrmPO`xlHUg5hhCw-)?)4^oF3F-f`+*7MyrC#a63=T(xD*FE%z~9
+zaT8bqV+)j^_-=6-{01qcT!{TJ!$pln+~Jr9_<2@PtS+qLUUtwC8Q>Nu-5%6~^PzvC
+zo1=ds+ZX2J0VM`LG}tkz*>v$4s^;~>kpjPby-({Hzy+^}v-6lTgS<1>`v~*#vcyWi
+z?`4ZUiBqH?mYN999XAUWT&X1PWwgK`wk_WKi5FU^JhKGkWb!NVlGs^{U=pm?QK-CU
+zWWf=eaXT3}#@_Q~<QZvZ7Jea-nKSbr{!DIV4V@twAE#;Y9URF?k2!;LXC%jcc*bo#
+z@nWNu!Ue<uO!xJN!{(5~5iyTMfD!hi^&19~Ci34Ej2UAb?7R_mr4J%IP_D)!p#E$W
+zw$78ViSsbN_<Ly}N2^s~0N-x?14t2I`<KcH8w!uu{ULFCM}<WWA8!tB)L~-EEdWiW
+zruP_1)S{}T@`qCe3+b9R8KZMg3=)JGCumh~S5>!>0IQsSpkd>7i#Nh3;t0kU+5PP0
+zMiI_!;g&Qb9}d}YXuXawTnyYED?#S)G8y}`fD~^jt)ytT9~#iLN!LxkUqXxzp0*v%
+zLkr_pro5p)M+XxsqSRK{KA!ocHxK@!+=vRZX>m<+Qx7{wf9Grs0$af3{}b*!#Jvjd
+zS|VEESuQ)cpQ#LIC*}>glFLaLd|guG7zcka;q=uEy}28#cgddxWO7KN!_RS4IMbZX
+zbL-dvbVAU|b(SR0G~QzmTG^G5i)KCN1*u>3bS<!!hSw+ebIq?yn40uQu+UVfA0`oi
+z&{FBz2;*mEY~x&3BQw|!P{z1sC$BZG{U5D(OCt~6k>7C(ld*Moo)sxmZX5yU{>Re4
+z{p6=LSwiCqLD<d%-70uM#d@%60hYg+Bo<MGCnM=R?prksDj{%V9AGpb1Kvl_WbR!)
+z&671lOca%=vU)Y{&x1!B;9h%ROeSQp8#0nz$l(&JST^ooJbhaeB7X9g$nzw7oJQ!^
+zXX?yT$d<&&RC<uDx4mMeyZzA>_ZsYU;3EMOy43lFIq}yyZ-L(tfPBvYE__#{mbbh5
+z>(AgQO$#>1n$Z3f;?fZ&r>j>A%$++w21e@+Y&PZS$6R;;g3_QDeS#B&vt@94pUBaR
+zv=;iU4F_ZCenA`cUj5i6AwDdEkJi5l9hjqeVbqP1TvESejJ?KJSo{en3vUJ36mTM0
+zg^TseKk%?5rW#zaEV%SJrnFV9EBzcl-0QASH`tt%Z6MAy3T4&ZBIX@sw_up?_FET(
+z4U9EudcY`<Y#l?^>vI5D7a(OCTX!Y#FE|6P##-lE5`OxzRR!h7x(wQ;gvW5~@Z^W3
+zdci`VIQMZfd^}?FLu5{qLr<S>P~%UFcXwnwStf{>Z|B;;BH1l4S-)Wa8D~c=skJG9
+z0RRw?{`WX*WM^Y<_up&;|3jQzR@1RM6#XxWQW_^PSGDDa8l5QwmJ*~3Wad+TFurN=
+zrMhs2g4m(8;QH?l7ar!cI8kl3bv_DvebJ}O49}}6qlA)XiiM==k?l)>8jBtO6yiZo
+zZOERXD_^=bNbT64B^rvm?093LVWJ~HU0dHp)#`C1`t>H(-ObYI(><OnV-g!oMC0_=
+z108BI;95+W&zH#v-{irRB8z^=^pZ!=iE0;x)D9i_l<PKg`0ZUi&Qxo#b)yQ(3$E-0
+zs(vd%H-1}Nz8X3<zMZXq(o7P}#esAbEu<ab1-er<MSyBsVid<}Tz3T>4mkYk=i#Br
+z_e)h1WPWmA%2+<foNFo>BA)^g3BKgJ9S-n#uYT}D$Erw{Nf{U%(`3%rH?(-nx;=FH
+zboiP}0_dFLN@4&%=?nOte|n~L(h{39kJ_mceK?Y?V!p7k7DU1&0i|%|AyFWFk-~cc
+zmC01vA|~AgtC>9;YxjOwMng*7M$@>AcrMCsPNo@RnzV`*vJAEt$;_tWcWO|WVvxV+
+zGq=b&R%66R;x!fpqYRIhxugPByfjgwG76geoZ@3oi@JYlcA26h)$L`ow#CtjLTN?$
+zP4)OLFDnn1ja0TyURfo30}ea7KfVX?GM}HXFO#vLtxhY%TFJ<Hgd}StRBf^#)aE6$
+zp<k`yNRqwA;xjLR(K`FmAS8*x%;KunT)U~EHo7#cN~D_@&!%kj130P^GExIU?oxo#
+zhnIHeqYGF9hvBTDN<amd<z|MNE7V=GH~;(%_YGRwA#Hx%PTzf<p~Wu-b;quAOiPG`
+z>4Zk9I0iSUC7eV|y1i@|D+^{_Yj+oG2{9+$*9g^$GqHy@VPO5DVZ=1t0Jxdonsg2K
+z%Lbigz^LO!nY(*{7~)>K-xm@Tm7+A{jFqt~tezf2m)CJ7FTCHc?G|M4gpMewmn|jU
+znClpU@l(v1cpF+wgMCTsX16VU<4#{TETlU`LeN6`%iCtrck7F`OqB)10t8G_L8E5;
+zvP~UqO?U>3>TmPPva(!p5s~8M7F24W>_^Z^No|w8i(MGn14Qw9ypcggI_*QGC#l}-
+z`u2U0BoQ}_x>Ba)pzZa?Cx&@!$<H{G`_OKUTC2n&b^Xw{;NI3FwnWeEb&&Nv|I74T
+z80gYwoUgRRuE*&0EwfeLj?Vin(d)rOZ4ZLh*S9HNOg8>Qzu>r|FI|jaCae^(#DbNJ
+z=?lt?^4&nh_htmG`Afy-tg8BAnZ8x_X5YaR=4HuGwZ;`Fw@P*P_Lph}Xrbv%G|A%y
+zoX%jgV3bIwBA8O-@hm-qV?(Hv3HS^f>O@Ac7Pfj|t$&Q4X&fHxhsxxcU1ns1F?vfk
+z$Otth!~q+kuv9umVATh}7ph52And)SSsvy}(APw5N~?D0@!oZ)w*&eI@IPlD*GmNM
+zxqtbpg?}~_!vAI6U}R_O<m~8T<our|xBp=hy2iGO+Z?(7{DvH<5jd|hnz1!t4xF{c
+zBDV1#p@#=EuPlR>)84Kx5l=7jR!#hRotffoqf=bEpRu2Vf059fnXxi6Gc`-)jyNQq
+zJ!EeKN8Q)^o8hB@>Y{Y5Dx(q7i2p^au_-e$n+QPLeO_4+_;Nfx-a*MGFmz`YZ_{G3
+z3|v}JPp+ie8bfxlSuy3*V#&zQ3bjyVAk$*dxQE#0`2zMf6<PET?MGdf*SrHH-?-cG
+zab%oK=7O^(E!`ti(MtV&n%n!$^_?>lu};%BL7UR|h!nU+mdUN#Jk3IWTB^69m8r91
+zj0+GbzRnqbST&Zxp0h-)2i4%dKP&!hWTCD50|vZuM^zSC?W&U9ZN@|I0>l|vtKMHB
+zY+tXQflpzynW6;n#yM`a;@Ht)TG`5Q##!&-gYMl3n_;C|k00=xiTG1=Y}sl6LZ8?r
+z*TCIAwJx=sZ#Z2;85|vq8s@=h6QNUI5|o4)gioOu0TB)c@;OJ|kZ_Sut;0#f8-^9Z
+zladx51pdD7LqmF5)i$94R;!2d4ExW1O9oR9UZ&9icu12$OvTRvcw4hZn!dp>#>^4V
+z2_1PL3XdAOSVYoS6*^1mu7Y|+ZczWYgiAJ5upXc%!bDgV5l5K--Rjd8q3LW}!z3TL
+zDlc=xV|sOM;>yVlc&8cksPGz8WgnhiHAZ`G+qh2D_Q!euj!@6A&e^T8)96BTFdXOr
+zOO%258b44BBp5AN7)#zntL44v3$Csi)jeP>h0~MRs(}2Fc|?_CuB7OOo-4y$=~}%M
+zIM$ZtC$Y-ZwyN)(zEFbUG4?6Qr9H@^iwYU<?E8;Mw7YwsN{p)p%RT78>{DNxlS_a&
+zGY&}=s^*oJj3dfw=QHQ0{w{)NIDp{!K!kA#tu*HkE>uJljxs1Ivs(#<V@ox<qiMWb
+zVKG-K^G^H2MP^ldP7`_Q&%2qqkfHif6@3S7w@+uV+q+KF*jE`X!JjUcn>I>B!-II7
+z+FV(UQOVG&Hpcp`$#Vl-Fbrk5Y3M)r(SG%YNH>i=S|lEd-HbtVfRPT;<2u+@2o<E<
+zLJ<vaT%dfn{#AX<@prA`u22|#?j2aM;;%3n@BFy?C-8&~R=`itd1g0muGSPA;?nB~
+z0piDny%X-ApuU=%=7(<}a9+5y^m<w3edU0JiT`{+TLypjR^fDatVsd|3`H)CTKPIQ
+z9!jZiVd%c+*YFHYqH#0q?jTO&FvmOD#H4RFN6cRgCgpX~f{<(z-nDNs{6}xao`d!f
+zUUM`qMi(&pLqN}dK8bfd7R@*g!Ax~|tDU`aJLjVMeDN|g-cmuR)WM4cA>Pa`vK@d!
+zm}-`97fSW_H*9NnZOOdAE>O0%%UwxijUxOhy(V86$b>H<O)$4C=aS&|xK?dxYGNH6
+zmmzdaJ*P=Ov=sG>RIeg*a4LNI&>|wSkd-Lrr%S#g3mM|i-<$UULqLEPscQ}I30Yh$
+zi<hlRb%7h9@`7X2xj;!+bI}>Gy{0@ejsG>!8cz||w#)vWm2i7bNq}SYymRO2DvgpL
+z663w{DP*)5OuM903W+Z$%f*sac9jfSl$<Kn-~`L6;uN&xu@wbbfDR0;NW$H`O=Rr2
+z&W*d)F6Vafw~=dRc<cglWFLq9Ri&;m4FFmo>F7+tr~t6LZ#lIsW&Bu;@LvLqS23$B
+z;jOQjTYRTMtRmtbv^mMlE7+ny#h|4Pgy%giiz%zBF_oL0&7CQCI-5=Tc9A5|m<(r%
+zy4X%pNi5$C6QZ)Iek`(3-ycU{MzL0R8J31iLZKK`d2UzfI=Y?(q9$+3Naw5>Q#lSP
+z83)N{Q0p3WmZ<V{j)3sv^IfHMZgq9fmJl+oTx1jc5V;bq`y<r4_6MpOM;qu88&*AV
+zwNq+V{sIk?lV4)9h2x<RC8M#Rnx9G7<)K4oVlhflH)xUY`$5EY37!(6M+?vwenL#K
+zI}NPtR;N70*W+3W|LmM3^j|tnqJFS1nij=twnB4Pb)HTcSPrW0faJqDuGZ?D_whyL
+z&t1o@F*@C@c~G>6Dw{xiip;YO&0Yd;ru|)WuFj#{qmM8!L+6KQ;Ev!b%wHrwZv<`(
+z#eA250E;-5CfBfpoUge<pyB`{)f%y2r(0!AbWs<3qRr<zISIM?Nhd$`XRv1=t+-T^
+z^wA>v4L~LT{ha0fEWgp|oRyWqf+V)L&@@*lawdnxizZQV-^m+gRLI=ra_!n<n(9Tm
+z><$5gua0wd`=|48ewJtIGopz}ENnqLli;+m4p-Z_eSrbT43y0i2y{dA!}B=#N<~vO
+z5Plxk6G%CUnN;TX*{Xl0$E<fR|4SIWO&w@mRJjwWQYr<`)(!}k77QE@Jlgn{LF630
+zP#iKa|7E2mQnD}So-7c#Qm}a+_CMQ+X1v~tZa9eP;imm~G}!yx%Hy#rWwSMm8y33y
+zuQc>LB(HJ1%Bf{LCmKwE)QiQXZer-eM4SQF2FGLBSk=lpV%p+=iCnhnZy{~d0Avxm
+zfrOal=mO17vn?5;7-~jeE0o!LtJFso1IeylPqQMm(Z;-Tb(;8Om)WM1sm7FG{@S8R
+z<b!9GYvs1`+|`X|TE-=GQV<ySn!r!)UNf!$*Zx)OC!Wnao}}wZ6V2Yy&rTih{scNt
+z;+5!y58(A8MU=3Mg<?ZNf{0cDL%gaLeRxYB13M#{89rNOz-rfhdOw3|(@0SE>h)k-
+z-m#<OdBN?EM3U1Dah{Tga=sdjw8`B2Bpm$ILqI;h5>Pyx=HfK?>u)FFPy^&e-w?z8
+zKK9Dm&zB6yz={T<j_+-JzpMbMe(s_ubfGSDL@`NkLcVj{6M#IWHhx+-)K3Jn)(ZPT
+zw$6*3N2OwDk4z`$EBadQJQG&qq%&Wm2!WR)A2Kq{TC;{i-QL$n33}4J{%0d%+yFiF
+zH>b<{+k5u!#qhz`RaEiwsE$w$!B+`&hE?_8By1b=`m2zP?#%w~flT9<2g27JHQpz`
+zE?+OLn+jh6M}0C%XK44CQSP4QoK?ch0H7M5FnDJT;)c}0T<PMu6dpB$U*z4ipWfG{
+z*$;_m@4cbJi#rDjOxQ7iwiDo$B$H3XukH_QD2yDo*zcLE@C4*Y*oMqPRSij%jSj+g
+z_M0WHC4vb4yo$<Ai#ph9Akc=6zec!y)idExi98lOBMVgMhBJO06BMwfGk^kbqMHNu
+zK=JD8q|gq6Z#q63(+p6~?@lW^LWoGB(Xx6eV3S9wC{DCiA;<`p3%y0S=m(|KpEH8n
+zO-GAw?YZHvbc|6C)`(>j2xgcZuvu;~MdFRoCi*kN2u2F&YxnxBnUS?p5RxFno3(;)
+z4C8XirqC5?Ri-hJR3rc6Qm4k$Bk6R&pZpOB#Bw^|=5%x&%RRm9X$vt-yMl5ho<h0|
+zjm)kwx>${wKiQ?}^yM0RmN_$<05G!w<&9B&#D06;Fz@M(iOy|lVe}m^nP6=0S6QZb
+zI5B8u>G(0)0nF+Gm1g}US|nWHDH=#C@NbeZ<Egd1e1ii90b$`AMi=6%)Qz`HDWyxc
+zvmadSvj;T6sQP>qmH4Va^ZCp2yW^=h*;hA1;~NrZ8$xZ*J^kHO1FE5v5ViB)+5wAx
+z2Nxn#H*SPH%1^W5{wn38CXb17{7?r1Fy+xz23SH+6f6m}+61<>iLp3BKs;4m*SuhG
+z+*>zELF-VZO~1xMsY(rUb?#k=Jfw-%_Rs=~;=6uN>@9d&euo^y130jJvia*PJf7GE
+z&wf|tv43}0`fIm)qY;1DS`VHsI2e|X)hz#+Ntlfsgns(jQMdcc{i2|~MC*(PT*d+#
+zki1`+R=m1~IV%ppKWo&~N0BnX`Vu_qv22u5PZJ>Q*nqOri}m@rJU<Xru00}cl2>Wn
+zzP8l*Z4c1llAkj|R38uIaDy2(F!{3aHc99#Z&IbLvkq?Zo4W)AT#k@FuDsF=0XXPj
+ze2-)-RpRf>QNG}`%s`~LsNdcwH-c2!fl!RtB{c>q)lv3&Lw@ogd2m1}0B$z<DwOeu
+zjACIqTc=F3Q=+bj5+OrQ`2lRz!e|h%lFgjFhy}Llab)51#SnvsU+g^rxc_1=ABQ0k
+z-_=tVQ~n}IlDs7ds~p>~>nxiPTTF$7DT$mV08_BSm5T2XNd!t^%fkDQ{<W$g8e>^D
+zKi|P2+U`s^ifu?WI5F(_uuY=OlIEe?PgDQ?I?ZVPJn5jm;Vef~ck>>-!KHtssoibC
+zt-frn9*t{xK4Y2ey5fAfX?1lmy^8NXOfj9|b$5VT;uH@{e^oGdo$E|}MKv7z1NNmz
+z>0meEyh<o{S1nj1ae9u}<@Sl`a+fg%lC=dSdnDgqMjpk~vd-#7h<nGv*b0@v3?<By
+zND=!^m82gLAdXG-6MBPdGf)_!O#)(x5+>L;_y<3YyQ0*53yBx8Z+a<3Uh}di*1gv=
+zpik=e?w~r3war_`(=RBBER2wo<LF`11;L4cgXjq;mn|T!43R9fz#f(RT+7Td{Eqzj
+zn08WNVq;6g83MY2ALdnzVTb_8u@4@m`{PRCshyr$@v^zeMrMQ+)(CnfT&8X_@+*AD
+zCm6Lf_%-(yOvgNH2AF+Cb<YgjZF-Sv#aVLm@S)kms*C)<RgCP2^v?a6P;XT2N1@xh
+zCaSPqb3C;Ix1gO#Y(c2$a)%iNMjAA_>{Wt7eK=N5WLQxJEIE4A-lWv^ad^ItLR?X2
+z8I2py+beOR7Nj%4W8t}s?IoQ{z3LQ|gZ!W=M<N)t(Y$kF*=sp8GetHmtypc{qEq$@
+zQ1?}ZhLMPQvY~Mg*~C9i$+(V=w;7yN6^{jx#p)^`y!?(LRETxPU152t&Uuzb-VQ}Q
+z5JzizdQw!?xw#&m`>fLqocR#qCMii8ODGs`wx0XBOEJ`KUSiH{^&iqXDK*N+idsg|
+zPDUfFSZQYI#z5|2c~Or$oO>~+OR3vDY|6KshyXP|G%PJWG;bCK6AJ>T=FZ^GMZ@N=
+zs2xP$Ebk#X?<x5SA)%t;lM=j0LfQIZt7~{M=5f|zC(O2ixSPH$(cL%Hx)4iL>nTo?
+z;j+OSYv_9m(9c1^OuFf8eGm~mP`~%BQ<~0={al&1toiac1bPY#;8`gsq+?xwASi54
+z6k6s%Z84zOjtVpj`lgIVaWl&XTyf}Wi2ypl+Zl;QGjRnbx3I$vgubi>3wzQ1P#;SV
+zuN#lLTv)I#kOAxTik0eoWgxAF)1dl%0Cab<*g9rM6>QBU7H6YSYrTOAgS;Xq(&>`-
+zV-TKh(0Psk`h4~)0~oLH+!Yn3^UZF%f^x2;suNen%;VWVKXvz(JA*z}bD{<Txt~4o
+z!@Wc}O&zQ@b+@90|JomazHi27$O-|)L@;GET_Ck-Tvq2|a~<VIkz-WY<Wm^(EN5C#
+z(L)IBQBN)yEw0Y*h@iD>HT@F;1(r>p`vks>!;m?NF*bM#dCNK`Jrqe;PfK|Xzpf`W
+z<nEguuDNXNt>?RIbq6ygeoPuyUfA1<*t|lU3*4P49D&m9l7~c#R>_fhgib;sbBMWw
+zkHWwo083!@Sk+EEUwb@nU=->`I0W$h5rqY|Eimh*xIE&CSD_)qJR%qHn!7rEZ!c$H
+z3{52%gh;v`(RsmDFLmM`5UWIo-B_2gPXibCaQy;EwhVl|cWA<Kf!n|`br%?d34z;K
+z@>#n${OQ7N97ceI2lR)gA&z&v|IC&A{<RVs{I_Ac1O2~gftl!xJPd5C|4&<@s@t+b
+z^vFG@6rQaXW4L|BYz1y3=-tdE<@RYY#UwTkGo`K8G~a-Qq#bGCX!bu|J%jHfjL{HA
+z=!GmDddR23{mJ+!V<NGr!#L$a%Mln2s|5Q6RvY*P^3`wgCe6q&16&LA!kMb#F_Bn=
+zC}Y?tsW8i!MKGGlfmAe|icsf<a@n)Q@&jjBz%@Z)f>Z*4tMoJfBxSmUV&mb8<b9pk
+zALlk9xBL2S%_Ooj4Ll!^mcb0i%0^wveHZ`o`uH&r_KWo>YKsm+>W;eDxMA(I3+P~@
+zy>xJ;ts?fm2$GH~V)DE}34-jZpL}y2r-KXE&02W|e7)@oIkv3P&eBH3ZqO^MfkM1%
+zmZUBDM3U4HXjv)2>%<dOi!mxKO$%~J5+|koNmtT2Wm^`}j(u?@ji(X7d!&CKVq!v8
+zQ|AA^TqBk%5#3o91gA$71a9ifuv*fE)-KAre)yaKE(t`&ozX@8-q|Cq?K;H`KpZ}~
+zGbW(Mj71${jyoH&q$`z0Q?;kBm*QyoL8-Z|^*P($D~fEqRYllfW%kUV|Bi#BFJcjm
+zOj)O|J1VyjW4)!KGnr1_)sl-bS(&Z~4}Prfrn&v+z{q_p*|q*RFbe<I8W1KrV;39y
+z|1A3YpYmr))3QVO@I9{-ZPzQzp8%v@44%(>xG@B4va1oun0YlHPYbA;<`Jj36M!dv
+zp>&TXNsKcTBxVnu?)sRxoh2YH<YW5-7yM2OS}DtttVh5wm_U*Gi;^Z|*-TdQW9&aQ
+zJdm0on8JkP8zjGw*BayLE5^8IZ_F=LV<s2!&_Ai|HEhw#51-UTFUs;rk_vhGPh|nC
+zAoHStR+0VJaWF<WY|5E<u%~7ed1=-xsjYJeBq`G30VIdC5F#d}4ihj`j|t)pd7FQ;
+zrxTNJM-%{jS`&Rp5&74|k=4+kc4<ZtKONE;rHH~Q6$T6o6d=*8*HqvNhR4O&<K-;c
+z0&`^*xAOUJJ`WE8(QjO=qXmmZ?57NQm(yPLIc|WH(N<#6{!z^TjLB=LJ>+$)?r-Ql
+zmi0%{n#HuzF06DTUbeA@*oYAh@i@uVr0-fdKL3PkbJe>V+C-^Gp=`sq{}8RnR%9z<
+zkuI-*KmWI#m=f5-1gC!ztclY94Q0keXJTXF{NG#}|I<6<vG|8F`@8#0wXs5pPa`R0
+z;l>KP+8|b6-qmI1)s^zd*ja!Jln@^dA#M&n*Frk}^HO~Q<)aLgSHq5b7zFF==Hjxg
+z<D+ks2y<$oK`NEv-6c(G(*O5${EzRk*|<taoBlDi!Kp{eG2UHolnHn4=w$y0Db<7t
+z;_SM<eP8TuHx6#k28XZP+Y3(SN#Zg#Keu9SSgjtv%X0>s`~B)EUl(SNFIgt_gN0%@
+z((^kP?AQDL<Y4QSnkw<HI!TnoAHSKyNhyA?dU-mJ<$b?j`hTk?bAKP@Ze)8CCEefU
+z{Y<vq-XB@j+dJBOvDXCJ0a2_&hb)sr1}=TF1Y)%%ql?*n$8@^F#h92!6JE1Uh@Z)Y
+zcat9K^<V5B&+$o>eEb?}pQd_-@q+?=(xO<~MI38-ciQ~W3U}^^Mt6_4@Wx6)8sT?l
+z4zojt4)MwRHYMV9k2sVr%{Y?B$%l&3lgcXTM(2blccAB?vE_S?pSGz3=lMjtnFjBN
+zMsE>ja({0P#-CA2y2NrzrfD^^WvK!bFiNptIKmF9JK&B;zAM34?xg!&*92U6<jdY`
+z%A38slFvv>{b-$5`W*!sYR@y#6|IVN5r*w4Pb}d%(dmSOeo(ttLwD!5!AZA6x3Jxq
+z-7XwlpWoJfhNe#upcq_XYkxi}R}m9$hPKGlb=#h<v+LK-Pap*`l@A*ta>p&~r!`{T
+zqbTGxk2_AV0Hbq5NJ6^b_juXK^tVj-1e7MA>qM7?;zK5Y4<^6)<iP>6#BQKUVi<Xb
+zNwxK)>4R4YHr2<XtmDPN?<a%wlyNy~UFmfbUF35O*I{4IS=E2&TpclJkRl+6g)8$m
+zfTTIyzP2@ufrr<;T?*)kY#j~PS-|u%S1{=MR({E#OjPPn+(ShBSTd%h6F3VFB)Wjn
+z_jYm`_4L6z)J5njCB-zy{^~nR#hMg}{g?<gkVF?r<UMtcjIFb{;kJFXE@~O&+}aeW
+zru8jr2T8(^VLyF0wFoXtgPku~5u{wyvC)VtVR1?AlJ`K9Is8Q&OHXau#8-<RN@E?j
+zsRx?*UUC9>_CNhd-5hmo>G0;*bv+_U-U_66-7~2ky0K&h&>7gn8vAL)$CVdW5@5>!
+zklf&D5xI#}wK);z)Vd=`@y$)-ixL6I!j6IU04a_XSq%{tO!SxyIMXQd*~l6VR%k(Y
+zCHM$egV0G;ZP8@oObr8q4Up*-os3qDD-0O30s(g00$`uezW#JEpbD?)JiZp6pD&vd
+zIn^gnzdj+vN!?mCh?4AeZW@+*uZPf65ad(~GGX*m`o@ryVS_+06AKW~Dye_i>-<b^
+z+8VzYHKLKuEG6qN$n|Cp8-$}6WG2|BweJ^PQ8X`mDZmJ-%Uls2{5mf%))MMfBYkwz
+za-@76wl|IBmU`vZbbJ5C17P2S%_Fsb%I`buUP*Kf#}4Vq@ubDQ2>DQkf3eLQ9rNTV
+zGHVK(j*1ery(gLv4RVwP^QGQ>GA~tww0w**THJ$dls`TdnkDvk#;PC~MD0)~PP#)p
+zAkdLA#e3QQ96*7M6<4vOG!tWYz%r9@A}SF7>y;+HEhDdGT+O<KRL1KH0KqN{w9xb)
+z7I&AQW09HIEE1PAneGP+sH0nKMENL#eQT9S0T5lY1Orc^o|i@4X3@!rfz)Kj$g+Pu
+z1kD}LxDIRwslE!Q>;O%Jc5d~cxS&PEKjGFRHJYzjBq0{&;6VB?@m)W6`|iL#hE>?}
+znvi+nSsV6-&0(bd`=@{$9NDmr{gj;Oiu)bFpnp_RW@`q~Js12F_QbfAFq{oMOd=1%
+zv#%hOXUybJz{mh=-cXv{M`;RAfH=A+)E6@IO+DIzBCb&zgR{Dkv$BHjL8sb0)wuZF
+z=)!OfjAt*x6V-m|YUtN3=@B6T7RC)f)5IQv@bde_MIEH{5*9LOf3&lKgOS~yI+LR_
+zPA&ycxN`&afY4p^NLjXl+L|?99Zw}WzxejDJ;O7n;_blpRnyf;+GrD|F8CJU=DO23
+zkz`I?&+eFE+G9f^NmQ)B2d)2J^d@Nk@{|l35`UF=QB)%Ii2b9#kcv_XH3j7T2#Yr5
+zTXM>z`zi~{EVOrjXraJ8Fkt61m_^uBjgo8lZDbQ%o?RvCMw(@KFitW|YeY@40QX6b
+ze(Oi-?Cub@g$6olT-q+Y>MVRtq>p4^AY`rhl$9D1uX73ekcM_%+$B1bG&r4db2z-X
+z+krU$Ys9cw`wM{3%}DxFcBvG86{Obj5#7|PNfv5EcDW}xzQEAI--^+`=0+&|LDl;?
+zEOK70k-HAuL4SXGf*yMhm8c+VBNrK$#&R6_m?8P0`#J{d9_th4J$cwUgAXBM{UD4z
+z80~7fDDy(9kP^|L?N@sBtg|C2r4D>aV58weey<Y5cm)C{&z!{wS2s5TSJcC*|LVWf
+zDilQ&=rz)yPjS*=n!=f6G|X#0Bac@~n+$mL$aS8Q1*dU5aQlgsZ0r|)dZ=(s6L;{7
+z7R2T_^M)YYcF?>U@WvCA^OHaOG6@Cm>IFU=92MKM(?SdOA=J$N8`CwP8_p*S7;7%Z
+ziX=ij37<xO6R!7@M`;}dFYF-pRX~9LHG+y#3v=XY$_Kl7;^^tu7tCt)X{%|L3{*Gq
+zrP1*Yub<?IC^D9BN}s5~`0g?Fc)Zcoc2Iy_hpm<v`uTCOs-UWGW=3WXlJsU(Ft@P2
+z<T00SVHIi7rcZwB^~&#EHrK7EbjIZzeXFU+-_d*NX;1mnn+~^1tsroriA&R$_S_Yr
+zIOS!8tEU@3rKRO;haB*pSfO=*OPMsuON3)IEeJlt#eq#zwOAAsKfPy;bV9yZL`r^G
+zEu1&BIr08?qO$VW1XSn8ib>#f`n)_jai}#~f;_BNcOB)Mxkt09WnUrU>%s&ogkx%<
+z4ZI?<NIN>2b00elqK;@G$m$57NN<LWJ~z=~T=q#tc&xVq_-9IOD?Yzw4nj8Lh&gW6
+z8DRCpzr;CVNcy5h7eR5~DLJVX$Wm8@SR4Z@VIwRK)acEIM8-BW0kif@l<n?fG0_Pv
+zQhf*3zr0XaN%ZO`f=s1~<=XihD`6(Aq2yY$@f7PC6o^K7<Fyi$<QNh#{VN~7;^HwL
+zu|r~3sW0xIm!&f-mc`BGN8w1h?kn3FJq__SjL0R`k=b@6`gFpY%*Nm_9gr}$*wOq?
+zx{G~^)rjYzf(&fGqMW6%E`TH9U|ddpog~b%JQ>w>2Wt0#1Oe0|13n`hvG`Y{Rne+(
+zV~2Px%p!S#yM$rJRw=(N0vL0!WeR$kWlxG%W7dRZM6rcaL?WF|4d1V2c(-?~e3Y~*
+zH%TEGr_6KMzI_x1DGe}>Cb;I~!A!9wO%op)LXxSf&8l@>!vshxV?(dEps1=068%ao
+zL%nG8%LUnC<L-RV&kI;n?Fps(a-3{zlzn4HzCyhz*0(%%a{SJy2K~8yMP$Y%<U31A
+zqwhk2sl|-ZQsYOH*krx789ZRz<2ck;>7lKy);KSR+oK3kEl	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..b849cc5e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 87f19edd9048494b22d7c18c737c54844b1a8138 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..69a82b784
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From b8c3a3f553f8f45342f366c98bde08c5d50b98ed Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..ef86a84c3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From de1d5c096cabbbc63e597d1289350c4e33151f14 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..d20deff2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From bce68e9ff14ebf7ee69ad7e75bf5f13ab475c462 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..fcf7651d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From da598db70063a591e065857cf5135070f6d85704 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..05220be2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From cdaa39ca2ac2ef603e94f26da575fa8dca160865 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..829eea2e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From 6f9f088271161adf4ce7a4bdc840b16ddd95b39e Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..ac0197202
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From e1be95bbc0080eacb20bc5b263a838784c2a1ea2 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..63b4e7ce5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 5838eafe65cf0ebcda3d192318015326a8afb52a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..4dd3996a3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From 25c2601d1d8d8836c9673dcbbf852515297e926a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..048e927bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 3bfc68099983191d1bc095d9ff4491fff41157eb Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..6b09b9360
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From 709c32be819afbbb7c7aecc392fac4986e9ae90f Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..f216c29a7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 7a122510a7d4843b5f8c64c0a34a807757184a26 Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..dcdbc4cf3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 5c6fd7eaeba958759ccedaaa483fe213859cfa6d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..27c77b7b5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 1ff8338ea1d8a27a5c45492507ed64154cc2cfc7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..cbbfe6d6b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From c947c0726673bcb8d8d3114d0e39fc926040559e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..e4cb51acf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From 3fb11ef19d2265b6e29fc69191079b32c79225dd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..615d10bd6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From 961e4f93259a0c1176a53a34d8e8de592c03db55 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..d45aba3d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 2c38a8787409ddb50ff9ee93006d5e30a40110de Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..35e26b0bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From 63e15e8f89e0e9144bda18df3e08e96e8c9bfe50 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..5d560ad3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 3c207ac9ac83d3966a7a84b8782394dc603030cd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..d8ecc09ac
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From d38703153f7df501ceb48395f756b2427ee15e57 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..83b6b982e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 18261d6c2dfd563ac542eb69d12a8ad648575c67 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..9d370c299
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From 715079cc08a7672d51c376a9d884873469ffa009 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..44532ab9b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From cfbf492ce859c817703e4c986d9ab9dcea30223a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..78127d14e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From ac1d98381de62b900af89aee5127c901fbe82c38 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..17ee076d8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 346aa055cd0cde2cc6a1fe92d874644b06dbdb17 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..dc39f98db
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From ccccbe478ac05d6093a076f61aaa00d8867b8760 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..cc4e52676
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 0821f7664961e3dae0d0f6bcf939b1a63ccdaa80 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..63975a42b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From a59dc80e1a8292e3a465c69e79bd7591ed513eb7 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..9c54c8abe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 8463e94459382e8e21d0b5bd19a23356bc674c28 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..34886e1f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From e7353c14b2a6b877eb31ebd029c613c91f8b7f11 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..7806d9e9c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From c6e1f74e9e670ba789467888bc34d64e71999a24 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #2: 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch --]
[-- Type: text/x-diff, Size: 1246021 bytes --]
From 815d53cd9a095b0b67dc6f98ad9a7fa251edefbf Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Tue, 11 Jul 2023 17:12:25 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..5c49cc00c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From b941f353c129f73934853082f3f3a01cebe6f944 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..2be6811fb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 0e26bbae1f9f8d60c3ab9470b3685af1dde5b6d8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..3e4f6fdf2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 66324f8dc74715a5018d1eced225557c40bd7acd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..ec55efb50
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From 74d539b86ca52e83255183d96b93ff7492751b6f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..2cbf02925
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From db1ead991924fb71d87e02aa43ffa73eae60594e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..bcfe315bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From 24811b631e0eed92347880f1dac3f932f4b46f9d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..b8e804269
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From 19a4134596217540832ed394d790d7b509ec865a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..9ab6cee15
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 29d3ef4d3ff8c836bc592b92687a28bf873d0e0c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..0c70cc566
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From dee5266820aabcfe09d103cf007bb26b9db54849 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..122b7e9f2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From b4a40c4df5872b0c9c7293b3a9fa057e208361d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..726402a8e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From ffbd9840d2c2e273a0ce2ea8fe20afad034bdeb2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..22c6c6bb6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From 430d19123b3a7adc21075b1befda8b550b7eb641 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..aa67bd2f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 5ea1f1b47c14cf9aeabe7d8e22511d54b15e5f1e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..7322216cc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 14e4c88e9bd1a12a2f081dfb2709df9f78106ca6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..04e45f3f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 12d37ec6af46d3edb806679183ab2138e4f1f5bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..cbbcfd541
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From eabcf73f0e7f26ef021cef30950c7bb3d2442226 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..7c6e9b521
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 5e529b9ae17c15231f989c17fe1a09897edf6477 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..4fe596a2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From c046c3a31322c7c29ff6d7f6172df41da3530683 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..121da7094
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From fbe6b2937087db11f96e21b36a540270d7c2b165 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..cf9aff701
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 41525c748413405d0faf6c8fc9a345047e31a1a7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..5fb1176ac
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From fed4bb3273633e5f81fc8ba21c8b62255c7eefcd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..b46846478
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From d54d8c038a7e5f9d2168daa6e13362d1df7d17a5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..56b8bd6c7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From db66eecf4c5301ca45db9f71c7b2c3c26d5927a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..8f9464a2d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From 467b223dc84f52933b2782ce9d116e89d48a26f1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..9213ccd2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 11ea2c45d5b88c92b25ab8e8027a931df81c2abc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..67ea8860e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 72279d87372cf21d980c40d01fce2f59bb734884 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..c41068c96
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From 362e778e5e57054b45099f1432abea181987d3c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..8f0ad3a82
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From d892f81e3bd3e979d3677c39c3a9efc40131b452 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..e2d86ca99
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From 11c4ac862bdce901ef19864532ef6d17a85799cd Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..f050c9c85
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 502382430f1bee8d0a9031fc70541488224a1029 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..6f71729f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From dcbba864209e8784ecaa5a1cf80b346b39b2a5d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..4d44fea08
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From 9c9bb1e6320a6ffbac0b171d9cf98cbb311104eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..1fdda3a7d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 8fe1bc305aeea5d86de1b3c5dfcd2e7984fc5145 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..7a47c7fe7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From dce0e40c9689a8d4fe71c9b2e8e4decd72dcf574 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..33738459d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 65b17ccbea1a17b26e6191a1d7336088d0dd4181 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..7fe451b2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From 2c886d201c19085ab52065d47e4f86a82a77f991 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..3e9a46a0c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 57a9cc0e1c99b0ed2ec5dbfd3fcfce456cfe13c8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..14394603f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From a6388c0e97e4378a4ef628361a46ff6d02d3159e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..2b80d5df1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 93f578698844d7276e5b2f287ee7ff3bcfc049bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..85c1bc16a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 21a399bf6e2e2eb09c98d173f1d0975bcecbf1e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..6d69f4754
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 1d3c288a2eed8caef24356e2a92483425bee0378 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..00db4a627
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From 281bb19a47566c01ce44e7184640728bb56d4de7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..82bf8bb08
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From 8ab2ff875382a92a1a72cf82a346c4acf2ec6859 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..c383e39f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From 64e58e5721f4fccc9cdf49b524271265a0c776e4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..606c1318c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From ad723ba7e7c2204bbd29115e967d249827d3c8f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..05c33089a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 8c02ce2abbab65cfa4d5b0f306bf9e4735a7195d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..ebbaa72ed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From cf7b144c917f5bc067dd8fd2a4d98c859849dbf0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..939a0901e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 084f823fda5939442f48eb34d99c0ed48d675097 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..8d956cdd5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From d3ef06c2dfe44b2286b42b7ddcc66f7c9e6aa245 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..b630d48e7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From b52d7c5d0141971f2e745ec806f829e2c7c897cf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..764ff7869
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 4f013b949f6528ec70105c1fb8e292f23e3a13c8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..964b83e69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 26e431c821c260a981ca8ed28f21330c8ef17af6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..8a07505bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 5917914e415c728e130d828cc1ed4e2199b1fcdc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..aebb68589
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 484af9f2be3868f90ae736c040332dcdc04b5d02 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..101a887e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From f66a9fd1bf7c2ed117fe697b80a0ea4b071773f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..b5bac22cf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From 65ec9d5d69251746a2723f7d2455c23166f3d1a9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..79718261c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From da0a88f5311e4181a9c24a5853f6392209dceabe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..4b708fa94
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 709d7c3604cc8f958dbd6360c66cabf612419c99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..ff0a4238f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From c507804a610e09aaabe7c5da7665857ce7947736 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..b35ba1448
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From ee4784efbbf1cbd18390e2dd36f61dee1b003858 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..b59ebabe1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 2c3c2a4217d033c3f7157046765e0888bde3bfbd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..0b8f63408
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 2034f2d33b24d43c9f0c120ace578ca53656fab6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..ffa265eed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 82065e999bfc125461c2e3db17856bf5a1384443 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..243cb82d0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 1d45845ffb9a30ca66806ba165bea54f7d43cd46 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..683ed9275
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 5c181e07ca0fda2ea26cf8c8949dd8baaa2be953 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..143c178d0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From ba6f76873dadead64be57c215ba2f7eb40d0ea6d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..f0e642634
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 7bd632e11a5ab75cec3e20722cf8fa3cebb1983f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..97db40580
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 56c4ee34b403d69ed3c7092edee95ae6f3b6fd74 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..1cdf70944
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From ff1f1e74f5f81eb634d72d472ce18f8d543bf3c0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..df5b0d372
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 451f88ab5d1ea7da9737ef701a4309963516f3c4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..122a63af8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From 37fd19b56ba31634a34349d956120b6a5898e2d9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..9b0e77d1b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From 1c3eab0fed1d0eb33da64eb568e42971822655df Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..79755daa7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From f9977969e7fabad5644b57f53462954ed097e66e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..444ed5d14
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 70943d9fae75cce204052ac0f498e603f1c7f4c4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..6bdf151fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From f2704149ae1bc11b01b6be854a28f178e20d00e4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..e71056e1f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 95812437eba0154b91108890dd53aab9615f7860 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..d244eea22
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From dcde4bb2e1b32d676bb0ff16de80dfdf7df6b085 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..60e9e9309
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 230b5bf8184beb02a3fa2678def7f11acf916e37 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..bd55ca5f2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From e55569d0a3fd1aa653fd702705e09359bdd90685 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..da7b2be63
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 7b5f6f27a281714c6fef8e609416b7f405e8b1ba Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..3b6b83a1b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 07ad7958eab96a4dadae514f7f963fa73d356191 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..843f53113
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From 2cf10215b5e8fcbb9162bde48456b60786d828cf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..d03a7ccae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From a53e82fa963696988c743aaca2341390332e7e3b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..32304e345
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From fb3f43735f7c9b8363940595d12d7b59544f64c0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..8b0f22d8f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From d65b38953bfe1cc346a2548a8e617229df87058b Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..39a6d76c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From 61f3a341b7accd0580af7c7b2e8db4cb6a7f4bec Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..eeebd7be3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 9271668477574b317c72099eab587f516ae082de Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..33ec83848
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 85d1f009d933d356e65a548f29e4d5a1e78a72d2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..758bd9878
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From a45ef62144036b481e8432bcd2698a5f7bfcddd2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..9c6735886
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From dc91d552ba45dd34022f37a4d4f328d5de5c1e17 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..b849cc5e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 87f19edd9048494b22d7c18c737c54844b1a8138 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..69a82b784
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From b8c3a3f553f8f45342f366c98bde08c5d50b98ed Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..ef86a84c3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From de1d5c096cabbbc63e597d1289350c4e33151f14 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..d20deff2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From bce68e9ff14ebf7ee69ad7e75bf5f13ab475c462 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..fcf7651d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From da598db70063a591e065857cf5135070f6d85704 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..05220be2f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From cdaa39ca2ac2ef603e94f26da575fa8dca160865 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..829eea2e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From 6f9f088271161adf4ce7a4bdc840b16ddd95b39e Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..ac0197202
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From e1be95bbc0080eacb20bc5b263a838784c2a1ea2 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..63b4e7ce5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 5838eafe65cf0ebcda3d192318015326a8afb52a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..4dd3996a3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From 25c2601d1d8d8836c9673dcbbf852515297e926a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..048e927bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 3bfc68099983191d1bc095d9ff4491fff41157eb Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..6b09b9360
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From 709c32be819afbbb7c7aecc392fac4986e9ae90f Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..f216c29a7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 7a122510a7d4843b5f8c64c0a34a807757184a26 Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..dcdbc4cf3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 5c6fd7eaeba958759ccedaaa483fe213859cfa6d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..27c77b7b5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 1ff8338ea1d8a27a5c45492507ed64154cc2cfc7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..cbbfe6d6b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From c947c0726673bcb8d8d3114d0e39fc926040559e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..e4cb51acf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From 3fb11ef19d2265b6e29fc69191079b32c79225dd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..615d10bd6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From 961e4f93259a0c1176a53a34d8e8de592c03db55 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..d45aba3d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 2c38a8787409ddb50ff9ee93006d5e30a40110de Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..35e26b0bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From 63e15e8f89e0e9144bda18df3e08e96e8c9bfe50 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..5d560ad3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 3c207ac9ac83d3966a7a84b8782394dc603030cd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..d8ecc09ac
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From d38703153f7df501ceb48395f756b2427ee15e57 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..83b6b982e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 18261d6c2dfd563ac542eb69d12a8ad648575c67 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..9d370c299
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From 715079cc08a7672d51c376a9d884873469ffa009 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..44532ab9b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From cfbf492ce859c817703e4c986d9ab9dcea30223a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..78127d14e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From ac1d98381de62b900af89aee5127c901fbe82c38 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..17ee076d8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 346aa055cd0cde2cc6a1fe92d874644b06dbdb17 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..dc39f98db
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From ccccbe478ac05d6093a076f61aaa00d8867b8760 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..cc4e52676
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 0821f7664961e3dae0d0f6bcf939b1a63ccdaa80 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..63975a42b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From a59dc80e1a8292e3a465c69e79bd7591ed513eb7 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..9c54c8abe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 8463e94459382e8e21d0b5bd19a23356bc674c28 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..34886e1f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From e7353c14b2a6b877eb31ebd029c613c91f8b7f11 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..7806d9e9c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From c6e1f74e9e670ba789467888bc34d64e71999a24 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #3: bitbake-output-qemux86-64.txt --]
[-- Type: text/plain, Size: 6011 bytes --]
Loading cache...done.
Loaded 12618 entries from dependency cache.
Parsing recipes...done.
Parsing of 7305 .bb files complete (7302 cached, 3 parsed). 12567 targets, 310 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
Build Configuration (mc:default):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:a43fa36614977745fcbd6441d1c3c20321a8201d"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:402fc96d7b589d9e22aca46789f0320134cf223b"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemux86-musl):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux-musl"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:a43fa36614977745fcbd6441d1c3c20321a8201d"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:402fc96d7b589d9e22aca46789f0320134cf223b"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemuarm64):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:a43fa36614977745fcbd6441d1c3c20321a8201d"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:402fc96d7b589d9e22aca46789f0320134cf223b"
workspace = "<unknown>:<unknown>"
Initialising tasks...done.
Checking sstate mirror object availability...done.
Sstate summary: Wanted 261 Local 23 Mirrors 228 Missed 10 Current 156 (96% match, 97% complete)
NOTE: Executing Tasks
NOTE: Running task 1089 of 1443 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_fetch)
NOTE: recipe python3-behave-6-r0: task do_fetch: Started
NOTE: recipe python3-behave-6-r0: task do_fetch: Succeeded
NOTE: Running task 1427 of 1443 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_unpack)
NOTE: Running task 1428 of 1443 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_prepare_recipe_sysroot)
NOTE: recipe python3-behave-6-r0: task do_unpack: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Succeeded
NOTE: recipe python3-behave-6-r0: task do_unpack: Succeeded
NOTE: Running task 1429 of 1443 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch)
NOTE: Running task 1430 of 1443 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_collect_spdx_deps)
NOTE: recipe python3-behave-6-r0: task do_patch: Started
NOTE: recipe python3-behave-6-r0: task do_patch: Failed
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Started
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Succeeded
NOTE: Tasks Summary: Attempted 1430 tasks of which 1425 didn't need to be rerun and 1 failed.
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
Summary: 1 task failed:
/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch
Summary: There was 1 ERROR message, returning a non-zero exit code.
ERROR: python3-behave-6-r0 do_patch: Applying patch '0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch' on target directory '/scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/git'
CmdError('quilt --quiltrc /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/recipe-sysroot-native/etc/quiltrc push', 0, "stdout: Applying patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
can't find file to patch at input line 15
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|From b941f353c129f73934853082f3f3a01cebe6f944 Mon Sep 17 00:00:00 2001
|From: jenisys <jenisys@users.noreply.github.com>
|Date: Mon, 11 Mar 2019 22:37:04 +0100
|Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
| description to created Scenario.
|
|---
| behave/model.py | 1 +
| 1 file changed, 1 insertion(+)
|
|diff --git a/behave/model.py b/behave/model.py
|index 4ad4b9d..9dd68fd 100644
|--- a/behave/model.py
|+++ b/behave/model.py
--------------------------
No file to patch. Skipping patch.
1 out of 1 hunk ignored
Patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch does not apply (enforce with -f)
stderr: ")
ERROR: Logfile of failure stored in: /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/temp/log.do_patch.448638
ERROR: Task (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch) failed with exit code '1'
^ permalink raw reply related [flat|nested] 4+ messages in thread* [AUH] python3-behave: upgrading to 6 FAILED
@ 2023-07-31 5:08 auh
0 siblings, 0 replies; 4+ messages in thread
From: auh @ 2023-07-31 5:08 UTC (permalink / raw)
To: openembedded-devel
[-- Attachment #1: Type: text/plain, Size: 1247077 bytes --]
Hello,
this email is a notification from the Auto Upgrade Helper
that the automatic attempt to upgrade the recipe *python3-behave* to *6* has Failed(do_compile).
Detailed error information:
do_compile failed
Next steps:
- apply the patch: git am 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch
- check the changes to upstream patches and summarize them in the commit message,
- compile an image that contains the package
- perform some basic sanity tests
- amend the patch and sign it off: git commit -s --reset-author --amend
- send it to the appropriate mailing list
Alternatively, if you believe the recipe should not be upgraded at this time,
you can fill RECIPE_NO_UPDATE_REASON in respective recipe file so that
automatic upgrades would no longer be attempted.
Please review the attached files for further information and build/update failures.
Any problem please file a bug at https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Automated%20Update%20Handler
Regards,
The Upgrade Helper
-- >8 --
From da0b2cd684fc6e99ddd93286ad943e0fb8a13fcd Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Sun, 30 Jul 2023 19:10:08 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..2aa6ea676
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From 740c382b73b5340bcebfc37bfd9465eea38497ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..11df1a986
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 14308a6133ea4f8c99ab8f244b8d53b8c66d4e92 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..d26fd8484
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 0cd772c65524c83d5b0b8e4f2717b50483bed6f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..36ba66863
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From 5a65705099c282a600d559b07d1e4d16be51785a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..2c812faba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From 013f4c64a710db8a6022672275ab333bd6f5c496 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..2946420c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From da5bf5d4d0934694a24f7bf961996ba66da82933 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..5f15616cb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From f8a98618906c47f129160a66f84e817790fb2293 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..b9587a50c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 85398f8eb5982963e40c4ee32064e86bf6903556 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..1f61f8b5e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From b644dc9d89decb155d2990493031e2569ebde17f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..ef5c54aa2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From ed452f9eab4a897abec13359a58efd5403323965 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..9eba76ba5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From 880d9ee23ce1d139bf65cb78eae3f072396d7e7c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..4fdcd02f0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From e9bb760d4d2c162cbe8cc56d819b9605194c12f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..daf80958a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From fb1b271f914f9fce62923f9325286a08a1e4b846 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..b7b21b4b8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 4ebff55edc60d51663fdf0a9e04fcd164d53d312 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..a9f05d848
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From df3d9f27e1dd0cee1be6dac0172018e59ebaf948 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..efb310b8a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From 0086d3b8beef7d618aed8bf6cee09e008d9051cb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..b2a19c485
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 46e20cfb65d91335857b29604dd2cd9f3e4ae900 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..f0ece6be7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 0838312b347ec4d0afbf916447adbe9300000615 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..be44f1f74
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From 73285170a852543b2931357e1f5582b989105175 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..cd05e15ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 27a8f299dbd4b8ade69bcd587c407fa6c0a7cc08 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..737e86b38
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From a4e37b34f6d49149cc02d5c2028aafacd2747bd5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..fd6c8dc62
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From 92ce934e25a5f6dd56a1b9eb15eb36591a9f5a9d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..56b5553db
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 9e4c804ed660e5abc3cb93224d6e69c4f448b24e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..0d84132c0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From e9abcf89752fc2b6c8ef34587e1e2cf29fc7c189 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..f09532bc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 4dabf13370b58b1a8cc5519790dfdaf4d48936de Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..8c49ecd5d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 60d44f6bc9675dd1811af7016b12d6c9760a2fbd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..ba9e4d96e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From d2c90506ea16e68c3f1581f021606665aed9b8b8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..5df02bc7e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From a6fba8892ab47c2f6832dc481f9f4280388dcd2b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..6b951c3ed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From f693ed1187311a1757c2d0e0c9ebd39d6ece152d Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..3f6b16bd0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 4c372ed7e46cf8ab88789c4d859595e967a95896 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..b5a866b3b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From c3e2611466f0bbac3835b98d0a50ed98566add50 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..52ddd5d54
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From fbcd6df6e8f21436a73c6f9a012f626d974140a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..999c2ebcf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From c7f435af294bc9687da18372002af666c7d5eb4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..74a4ffef8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 0257bd816333e6e5b15209dd4eaf64d2c3b528c7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..34ea558cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 47e531986acbcf178e7ebad6bd2c939da101e31a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..52b5657f9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From 618779b9ae0393b80cb7d6b2fcca54f4d0cbbaa5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..f15f2e324
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 3ed24342602e07db5d15460763e7be153b59cad7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..fb96ec28d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From db0d3ae7bec1f5f2b75f0d7b7fcef52ccf4c1de1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..fc05ec554
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 8a175e36bccbfb7c2924d1bdfc9df61dfa0358e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..517499d56
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 7d9063200fcf1a66d9f5593f136175ca273345bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..c80c1fa82
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 07dba400e47042ed44be12467de9eef83f941f41 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..785d964b6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From b89795774366fe2412e2e548e6bf5bc258a2e66d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..ed497b4ff
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From a41d3020216b220e482edad08ac1183ed81dc26f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..ab202daae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From a3e5efc14d45b95420227fb95e9cb742123679c2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..380a98e64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From dd136616c40b7ddd456da71dbd297996abb4c77e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..66df37b8e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 461d0364e76156d4173835cdca3e05fb246f9c8f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..52d5f2713
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From e11a4a989d8d74a8835728a7cace0de39cf945a9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..4207d47b6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 3a6b53a26cae69d8421e1548c10986e170c46735 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..697e61c51
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From f4e7051e6809a26c4b64c988791bc81a6e36ca77 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..528467a93
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From 79680999c402a712e66ac59ea438c8a9dc16c275 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..04efc7c76
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 12bd0a871d028e736530b40c1f61e55e22195151 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..c83c37b6e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 92925bf77855a47df17d5ac0b9ff00bd77bf11be Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..5a4e464ad
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From cfb37156cc15c90ac3ab033d86334a760e874989 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..5fd490d1c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From d4ee60c6e3fc2764cf9991d2d67d5b874e78bc19 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..4c46f5177
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 800bf34e1de4eb87b6e9977daeb46d454f007d8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..dd812918d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From c9c911d92bb9113d7ac5d908739dde9d1555201b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..bb3dd1d0d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 20e70caeefdd3b21f7a4f1e0493c763241b540ad Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..dd28adcfa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 5748e50c9980d09860e3cfc5acb158f3225f17df Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..98b1554e3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 6b22a83e090d94f7150cfc4508ee884611783f2c Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..2b128d678
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From 7f12779dc95737f67271b124f9e72491ed59f8eb Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..84540b0e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 23c0394754ef0ebfbc58d1ae2fd6e2fb37bbe83c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..87be6a134
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 5b378e89eb570f764c47a03bbcbfd8deeb6b280c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..ff141aae5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 869b7099535c877c7a8e49e031bb4392bd756548 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..53264a01a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 74ad75c1cc1005c7d965dee7701e48decd623882 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..bbae190d9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From b3e47b038f5904b52b814fe514669d01f4b3a473 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..7c5876509
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From 2b2d90ddd8be28eca109a4d373ff04ec86ecd54c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..8490956e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 497dc9790e37ca3f1f645f5535e776b6dc5373ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..3755c4fda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 4253754dfd41230ad56b8bc5193ca34837f8a2dd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..231562a3e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From 28f6420a77a7d25bf23be531967441209670f151 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..9c174da72
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 22859b5c1cedb7726ccc287d85c036a04ef59f8c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..04efa5c51
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From d8c65f6aca054cce4a11136b8a85b87ed68611a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..6899babe6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From e9abb13f68d5a4e16ae4d85a226e628d45587277 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..374ba1384
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 0f2288c312c37f39861cb8aceb2614e5655a33f9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..2ad9f5991
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 18dd6d52e61536c7e0f150ffd7d50e970e71bea2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..49b9f96f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From a524f88c18964c97ca36fdd9c66d088751b6ee18 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..ae1b198d7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 252af77a75e82e1690e72979d5d0a639d61a16c2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..a2f3184ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From a9446c5859217abb0a68243f05b7f5207b0fbb2a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..7072082c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 49d59f4d7bdc20bc7d0a88af37f20bd2711dc4bd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..9acf438f8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From 8f9317eba99355d89129288feb54042799ea282f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..dc8429004
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 317782c144e9c32a4e8b1b8350b27445ddce90ac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..488d4b8e0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4cc1d7fc94589e70186ff554d4368416d035117f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..aeae4f423
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From eaa760fcf0852fd44e0bf880bcf889d3c4ca0324 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..7dd73e674
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From 9797b1a92accef9fb4da538a6b559879f7262e61 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..c3535c58e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From 4ffa109395559efd821974b1fc543561e6c386f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..c5566fd3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From 4e9e3d5384ef0ff0a9e4a248fb04fc6cc4372dbf Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..af314d8e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From b7177d5ecef5eda44b0a6e0cd16984dd52d1e95d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..1846c9e39
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 118e72e535cc03ad2f1b4705ee33445c969d3e00 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..2e275ce25
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 9309a37be0fed5e64e9b9ef4a35676201a4712f8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..a843d7f75
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From b698e9a91b07fbc1155341142e297516a30e256b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..347073cf1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From e8a4a63ffcc1c6fe76fa9a90af8fcfeed3493223 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..aea446365
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 89e1b1399a986d382d998391d3bd282f7c5275e7 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..fa325c1f0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From 488653fb558568b12b88011dc91225889d80231b Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..299bec44b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From 748f942fd0cee218b335752944eb82195bad3b11 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..459a6b585
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From ec8ac46c4b7a1cc3cefc5579642b30c7c270850f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..32e90f5a1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From cf9a37a8317a5e49a0ab74c7c287b8a19349a428 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..f32343c64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From 03a87b668b7ed453421c86d7dc7f1805a61299eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..1e375cb4b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From ee8c8064e066cf2f4940baa7501b67387063509d Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..f15744cfd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From 8f2ce5f60eb169c8b943cf49f240b20840bce00d Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..df31fb9ec
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 760f2bcd40654d358ece1ebbf4d50f1a21b7fcd5 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..ab0bbbc69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From 533647e00afe880e4abe8fc06ef35a5216cf709c Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..55d43d4e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 61e7884c632f276aca3c452fe8e425075e3f4128 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..704e3695b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From cb621ce1412f81af390af256de1d5e6f9026787a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..c52168247
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 01a3705a0cb67cdbf0553de61b8197e8e7b53011 Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..db4a81046
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From c203bfcae7d7abdfb160931881364a978c304265 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..24711438b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 3499cabec43858e055f268f1d8a0609af76730da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..eba6cfb3c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From c7deb14c0b6f52460694d95df7718b94fe329bdd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..67a9c2dd9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From f22db2f5fc48333711a21af14b6be74f09043590 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..ac0033647
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From d192175057c8ab7b80ef7b1c472851cb8eae569a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..0701d13a2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 5bf2eead3be4139fad1a358d65c43365e63bd772 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..30bea7ee6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From ba1b10f9e23cb301fa5d5eab36ef7f0f8731668e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..573a9a6c1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From b04a6262d9c3585bb71d35fd380273fd7920cbc2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..db4ee4f2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From f6f2f722aa95b44f8a3e63c29113f26bb7201a8c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..e9171db8a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 821d3deb2bc21ddc84cf201e486813dae8ad75f4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..c8025e667
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From b9a1eebf79594aedf025f49f82773b3f626f056a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..9e0d5afc0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From 0b0c5cbf7f983d859ed53a7b088027310ec4481b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..c3275c4ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From 8376ff96527f51fc6207b7bb036a563dbbedba2e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..06b6a4d03
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 78a0c35f8a8f7a1ff371b96a340b5b39857289ca Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..cfeb000d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From c3c55905d6b3977ec74890256073ecac0c7708f6 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..435cf14cc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 24cb3e78b490c24265259fcc1de6184b3124f3af Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..eaef8940d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From f2890b5e15998b19fef907ac7edad4b6359cdc50 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..678b81421
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 8d8784b4514b9d2e7d4a3e14f683bc2c04d67f09 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..06858c6ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From 8d98f3c27a40a515b2b0187fadc463937b5877c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..2eb388d14
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From 9b73de83a86706bc8697666bda0ce5788e0db6da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #2: 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch --]
[-- Type: text/x-diff, Size: 1246021 bytes --]
From da0b2cd684fc6e99ddd93286ad943e0fb8a13fcd Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Sun, 30 Jul 2023 19:10:08 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..2aa6ea676
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From 740c382b73b5340bcebfc37bfd9465eea38497ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..11df1a986
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 14308a6133ea4f8c99ab8f244b8d53b8c66d4e92 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..d26fd8484
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 0cd772c65524c83d5b0b8e4f2717b50483bed6f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..36ba66863
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From 5a65705099c282a600d559b07d1e4d16be51785a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..2c812faba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From 013f4c64a710db8a6022672275ab333bd6f5c496 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..2946420c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From da5bf5d4d0934694a24f7bf961996ba66da82933 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..5f15616cb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From f8a98618906c47f129160a66f84e817790fb2293 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..b9587a50c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 85398f8eb5982963e40c4ee32064e86bf6903556 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..1f61f8b5e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From b644dc9d89decb155d2990493031e2569ebde17f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..ef5c54aa2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From ed452f9eab4a897abec13359a58efd5403323965 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..9eba76ba5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From 880d9ee23ce1d139bf65cb78eae3f072396d7e7c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..4fdcd02f0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From e9bb760d4d2c162cbe8cc56d819b9605194c12f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..daf80958a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From fb1b271f914f9fce62923f9325286a08a1e4b846 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..b7b21b4b8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 4ebff55edc60d51663fdf0a9e04fcd164d53d312 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..a9f05d848
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From df3d9f27e1dd0cee1be6dac0172018e59ebaf948 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..efb310b8a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From 0086d3b8beef7d618aed8bf6cee09e008d9051cb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..b2a19c485
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 46e20cfb65d91335857b29604dd2cd9f3e4ae900 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..f0ece6be7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 0838312b347ec4d0afbf916447adbe9300000615 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..be44f1f74
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From 73285170a852543b2931357e1f5582b989105175 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..cd05e15ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 27a8f299dbd4b8ade69bcd587c407fa6c0a7cc08 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..737e86b38
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From a4e37b34f6d49149cc02d5c2028aafacd2747bd5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..fd6c8dc62
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From 92ce934e25a5f6dd56a1b9eb15eb36591a9f5a9d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..56b5553db
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 9e4c804ed660e5abc3cb93224d6e69c4f448b24e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..0d84132c0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From e9abcf89752fc2b6c8ef34587e1e2cf29fc7c189 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..f09532bc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 4dabf13370b58b1a8cc5519790dfdaf4d48936de Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..8c49ecd5d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 60d44f6bc9675dd1811af7016b12d6c9760a2fbd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..ba9e4d96e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From d2c90506ea16e68c3f1581f021606665aed9b8b8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..5df02bc7e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From a6fba8892ab47c2f6832dc481f9f4280388dcd2b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..6b951c3ed
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From f693ed1187311a1757c2d0e0c9ebd39d6ece152d Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..3f6b16bd0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 4c372ed7e46cf8ab88789c4d859595e967a95896 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..b5a866b3b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From c3e2611466f0bbac3835b98d0a50ed98566add50 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..52ddd5d54
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From fbcd6df6e8f21436a73c6f9a012f626d974140a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..999c2ebcf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From c7f435af294bc9687da18372002af666c7d5eb4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..74a4ffef8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 0257bd816333e6e5b15209dd4eaf64d2c3b528c7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..34ea558cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 47e531986acbcf178e7ebad6bd2c939da101e31a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..52b5657f9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From 618779b9ae0393b80cb7d6b2fcca54f4d0cbbaa5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..f15f2e324
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 3ed24342602e07db5d15460763e7be153b59cad7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..fb96ec28d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From db0d3ae7bec1f5f2b75f0d7b7fcef52ccf4c1de1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..fc05ec554
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 8a175e36bccbfb7c2924d1bdfc9df61dfa0358e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..517499d56
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 7d9063200fcf1a66d9f5593f136175ca273345bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..c80c1fa82
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 07dba400e47042ed44be12467de9eef83f941f41 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..785d964b6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From b89795774366fe2412e2e548e6bf5bc258a2e66d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..ed497b4ff
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From a41d3020216b220e482edad08ac1183ed81dc26f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..ab202daae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From a3e5efc14d45b95420227fb95e9cb742123679c2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..380a98e64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From dd136616c40b7ddd456da71dbd297996abb4c77e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..66df37b8e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 461d0364e76156d4173835cdca3e05fb246f9c8f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..52d5f2713
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From e11a4a989d8d74a8835728a7cace0de39cf945a9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..4207d47b6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 3a6b53a26cae69d8421e1548c10986e170c46735 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..697e61c51
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From f4e7051e6809a26c4b64c988791bc81a6e36ca77 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..528467a93
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From 79680999c402a712e66ac59ea438c8a9dc16c275 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..04efc7c76
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 12bd0a871d028e736530b40c1f61e55e22195151 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..c83c37b6e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 92925bf77855a47df17d5ac0b9ff00bd77bf11be Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..5a4e464ad
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From cfb37156cc15c90ac3ab033d86334a760e874989 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..5fd490d1c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From d4ee60c6e3fc2764cf9991d2d67d5b874e78bc19 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..4c46f5177
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 800bf34e1de4eb87b6e9977daeb46d454f007d8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..dd812918d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From c9c911d92bb9113d7ac5d908739dde9d1555201b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..bb3dd1d0d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 20e70caeefdd3b21f7a4f1e0493c763241b540ad Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..dd28adcfa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 5748e50c9980d09860e3cfc5acb158f3225f17df Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..98b1554e3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 6b22a83e090d94f7150cfc4508ee884611783f2c Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..2b128d678
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From 7f12779dc95737f67271b124f9e72491ed59f8eb Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..84540b0e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 23c0394754ef0ebfbc58d1ae2fd6e2fb37bbe83c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..87be6a134
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 5b378e89eb570f764c47a03bbcbfd8deeb6b280c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..ff141aae5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 869b7099535c877c7a8e49e031bb4392bd756548 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..53264a01a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 74ad75c1cc1005c7d965dee7701e48decd623882 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..bbae190d9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From b3e47b038f5904b52b814fe514669d01f4b3a473 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..7c5876509
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From 2b2d90ddd8be28eca109a4d373ff04ec86ecd54c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..8490956e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 497dc9790e37ca3f1f645f5535e776b6dc5373ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..3755c4fda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 4253754dfd41230ad56b8bc5193ca34837f8a2dd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..231562a3e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From 28f6420a77a7d25bf23be531967441209670f151 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..9c174da72
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 22859b5c1cedb7726ccc287d85c036a04ef59f8c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..04efa5c51
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From d8c65f6aca054cce4a11136b8a85b87ed68611a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..6899babe6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From e9abb13f68d5a4e16ae4d85a226e628d45587277 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..374ba1384
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 0f2288c312c37f39861cb8aceb2614e5655a33f9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..2ad9f5991
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 18dd6d52e61536c7e0f150ffd7d50e970e71bea2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..49b9f96f3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From a524f88c18964c97ca36fdd9c66d088751b6ee18 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..ae1b198d7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 252af77a75e82e1690e72979d5d0a639d61a16c2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..a2f3184ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From a9446c5859217abb0a68243f05b7f5207b0fbb2a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..7072082c2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 49d59f4d7bdc20bc7d0a88af37f20bd2711dc4bd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..9acf438f8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From 8f9317eba99355d89129288feb54042799ea282f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..dc8429004
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 317782c144e9c32a4e8b1b8350b27445ddce90ac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..488d4b8e0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4cc1d7fc94589e70186ff554d4368416d035117f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..aeae4f423
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From eaa760fcf0852fd44e0bf880bcf889d3c4ca0324 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..7dd73e674
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From 9797b1a92accef9fb4da538a6b559879f7262e61 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..c3535c58e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From 4ffa109395559efd821974b1fc543561e6c386f3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..c5566fd3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From 4e9e3d5384ef0ff0a9e4a248fb04fc6cc4372dbf Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..af314d8e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From b7177d5ecef5eda44b0a6e0cd16984dd52d1e95d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..1846c9e39
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 118e72e535cc03ad2f1b4705ee33445c969d3e00 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..2e275ce25
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 9309a37be0fed5e64e9b9ef4a35676201a4712f8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..a843d7f75
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From b698e9a91b07fbc1155341142e297516a30e256b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..347073cf1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From e8a4a63ffcc1c6fe76fa9a90af8fcfeed3493223 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..aea446365
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 89e1b1399a986d382d998391d3bd282f7c5275e7 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..fa325c1f0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From 488653fb558568b12b88011dc91225889d80231b Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..299bec44b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From 748f942fd0cee218b335752944eb82195bad3b11 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..459a6b585
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From ec8ac46c4b7a1cc3cefc5579642b30c7c270850f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..32e90f5a1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From cf9a37a8317a5e49a0ab74c7c287b8a19349a428 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..f32343c64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From 03a87b668b7ed453421c86d7dc7f1805a61299eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..1e375cb4b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From ee8c8064e066cf2f4940baa7501b67387063509d Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..f15744cfd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From 8f2ce5f60eb169c8b943cf49f240b20840bce00d Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..df31fb9ec
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 760f2bcd40654d358ece1ebbf4d50f1a21b7fcd5 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..ab0bbbc69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From 533647e00afe880e4abe8fc06ef35a5216cf709c Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..55d43d4e1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 61e7884c632f276aca3c452fe8e425075e3f4128 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..704e3695b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From cb621ce1412f81af390af256de1d5e6f9026787a Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..c52168247
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 01a3705a0cb67cdbf0553de61b8197e8e7b53011 Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..db4a81046
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From c203bfcae7d7abdfb160931881364a978c304265 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..24711438b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 3499cabec43858e055f268f1d8a0609af76730da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..eba6cfb3c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From c7deb14c0b6f52460694d95df7718b94fe329bdd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..67a9c2dd9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From f22db2f5fc48333711a21af14b6be74f09043590 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..ac0033647
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From d192175057c8ab7b80ef7b1c472851cb8eae569a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..0701d13a2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 5bf2eead3be4139fad1a358d65c43365e63bd772 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..30bea7ee6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From ba1b10f9e23cb301fa5d5eab36ef7f0f8731668e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..573a9a6c1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From b04a6262d9c3585bb71d35fd380273fd7920cbc2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..db4ee4f2e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From f6f2f722aa95b44f8a3e63c29113f26bb7201a8c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..e9171db8a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 821d3deb2bc21ddc84cf201e486813dae8ad75f4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..c8025e667
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From b9a1eebf79594aedf025f49f82773b3f626f056a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..9e0d5afc0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From 0b0c5cbf7f983d859ed53a7b088027310ec4481b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..c3275c4ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From 8376ff96527f51fc6207b7bb036a563dbbedba2e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..06b6a4d03
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 78a0c35f8a8f7a1ff371b96a340b5b39857289ca Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..cfeb000d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From c3c55905d6b3977ec74890256073ecac0c7708f6 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..435cf14cc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 24cb3e78b490c24265259fcc1de6184b3124f3af Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..eaef8940d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From f2890b5e15998b19fef907ac7edad4b6359cdc50 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..678b81421
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 8d8784b4514b9d2e7d4a3e14f683bc2c04d67f09 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..06858c6ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From 8d98f3c27a40a515b2b0187fadc463937b5877c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..2eb388d14
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From 9b73de83a86706bc8697666bda0ce5788e0db6da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #3: bitbake-output-qemux86-64.txt --]
[-- Type: text/plain, Size: 6226 bytes --]
Loading cache...done.
Loaded 12621 entries from dependency cache.
Parsing recipes...done.
Parsing of 7341 .bb files complete (7338 cached, 3 parsed). 12618 targets, 325 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
Build Configuration (mc:default):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:5dd5f0f5348594a0b636ef05a87381b997db4fb5"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:761c4208d2c86e1261d7377c25f8a4f97edd1532"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemux86-musl):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux-musl"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:5dd5f0f5348594a0b636ef05a87381b997db4fb5"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:761c4208d2c86e1261d7377c25f8a4f97edd1532"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemuarm64):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:5dd5f0f5348594a0b636ef05a87381b997db4fb5"
meta-oe
meta-python
meta-perl = "tmp-auh-upgrades:761c4208d2c86e1261d7377c25f8a4f97edd1532"
workspace = "<unknown>:<unknown>"
Initialising tasks...done.
Sstate summary: Wanted 89 Local 0 Mirrors 79 Missed 10 Current 546 (88% match, 98% complete)
NOTE: Executing Tasks
NOTE: Running task 1337 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_recipe_qa)
NOTE: recipe python3-behave-6-r0: task do_recipe_qa: Started
NOTE: recipe python3-behave-6-r0: task do_recipe_qa: Succeeded
NOTE: Running task 1789 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_fetch)
NOTE: recipe python3-behave-6-r0: task do_fetch: Started
NOTE: recipe python3-behave-6-r0: task do_fetch: Succeeded
NOTE: Running task 1790 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_unpack)
NOTE: Running task 1791 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_prepare_recipe_sysroot)
NOTE: recipe python3-behave-6-r0: task do_unpack: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Started
NOTE: recipe python3-behave-6-r0: task do_unpack: Succeeded
NOTE: Running task 1792 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch)
NOTE: Running task 1793 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_collect_spdx_deps)
NOTE: recipe python3-behave-6-r0: task do_patch: Started
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Succeeded
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Succeeded
NOTE: recipe python3-behave-6-r0: task do_patch: Failed
NOTE: Tasks Summary: Attempted 1793 tasks of which 1787 didn't need to be rerun and 1 failed.
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 4 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
Summary: 1 task failed:
/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch
Summary: There was 1 ERROR message, returning a non-zero exit code.
ERROR: python3-behave-6-r0 do_patch: Applying patch '0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch' on target directory '/scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/git'
CmdError('quilt --quiltrc /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/recipe-sysroot-native/etc/quiltrc push', 0, "stdout: Applying patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
can't find file to patch at input line 15
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|From 740c382b73b5340bcebfc37bfd9465eea38497ef Mon Sep 17 00:00:00 2001
|From: jenisys <jenisys@users.noreply.github.com>
|Date: Mon, 11 Mar 2019 22:37:04 +0100
|Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
| description to created Scenario.
|
|---
| behave/model.py | 1 +
| 1 file changed, 1 insertion(+)
|
|diff --git a/behave/model.py b/behave/model.py
|index 4ad4b9d..9dd68fd 100644
|--- a/behave/model.py
|+++ b/behave/model.py
--------------------------
No file to patch. Skipping patch.
1 out of 1 hunk ignored
Patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch does not apply (enforce with -f)
stderr: ")
ERROR: Logfile of failure stored in: /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/temp/log.do_patch.2479312
ERROR: Task (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch) failed with exit code '1'
^ permalink raw reply related [flat|nested] 4+ messages in thread* [AUH] python3-behave: upgrading to 6 FAILED
@ 2023-08-05 7:38 auh
0 siblings, 0 replies; 4+ messages in thread
From: auh @ 2023-08-05 7:38 UTC (permalink / raw)
To: openembedded-devel
[-- Attachment #1: Type: text/plain, Size: 1247076 bytes --]
Hello,
this email is a notification from the Auto Upgrade Helper
that the automatic attempt to upgrade the recipe *python3-behave* to *6* has Failed(do_compile).
Detailed error information:
do_compile failed
Next steps:
- apply the patch: git am 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch
- check the changes to upstream patches and summarize them in the commit message,
- compile an image that contains the package
- perform some basic sanity tests
- amend the patch and sign it off: git commit -s --reset-author --amend
- send it to the appropriate mailing list
Alternatively, if you believe the recipe should not be upgraded at this time,
you can fill RECIPE_NO_UPDATE_REASON in respective recipe file so that
automatic upgrades would no longer be attempted.
Please review the attached files for further information and build/update failures.
Any problem please file a bug at https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Automated%20Update%20Handler
Regards,
The Upgrade Helper
-- >8 --
From b48d3d14db1a4f0c74c6532e4d272b6cb2e60e15 Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Fri, 4 Aug 2023 21:24:21 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..b55b16c81
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From e1f9e76db224f08d20fe57d2a259c430dc39670f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..1d9ddc6ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 2580877e6c3e4132b9fe85cfd117596ecbe9a2f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..9521cb092
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 476ca20260449510e9c15b3d8b8e9e1d30f9c0e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..b18d50229
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From e7c24c3c9ad789c1768b9d59a28e7ffc9efb2d0e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..1b4346685
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From 82ba73765ce544dd3fa19834222a01498bc6fe6e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..ad31d8a6e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From e7ca5f9e63c0f35aa9ee17fb4d2b8e0df0912bcf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..2e7b8c43b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From a1881fa5e4d9fe2493b9ad58c857800dec598bd0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..8179879aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From ff23dadb32389518972f7776d8e7ea3fe9102256 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..739db0fc3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From 36db6264555c1b4a9e707333cf2a85efbb27fdd7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..d0dd92255
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From f204232644fdca841292f618a78c5605cc5b9ace Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..bd8015190
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From f59928a5ef7757b9a6394d33005973c0bfa8b6e2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..be8fed6b0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From e2666db837cab4424984c445483fc6fa30dc0acb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..1f038d390
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 07f7fb53b8adedbef73fe8d155fafccb59b5df99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..24687bc88
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From a4198508c5701f51324278b74b5f75c53cd5bb84 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..a1b073ccb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 1990a3630cb6b5fbea3aea69131445642105551c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..5cf188b0a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From c7ac4023212c0193939d3046c988d58a2a13fc79 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..f081624e9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From debf8dcf55e7bd1ad6d18c832acc322431497147 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..982bbf649
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 144131735fe58c02fbe8ff51f9715c230119c79d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..c7ec34bc6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From e579e0758c1350f4d236987c592be94107c2c8a2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..4a907546a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 6f424ed40c509e007bf7cb8d56e6586edf558e7a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..44e37cb57
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From 94d152d86ad42378a90dfc10d614f95d9a4eefb7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..a2b3e1e80
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From c5c8623bafa8733cfcd227922e93713cf317ac22 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..9fd98b07b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 73330bab6864704929d9aeef41e8b8d3d59d5738 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..bc1a21316
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From cc023600296c2b6fb8711e4878d20ec69534d477 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..a9ca3adea
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From e698249e72c33d2964eaadf39ef4a272b54d74d5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..3f73d0e64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 00aa14dce6b67e815aa6270506ff5d3ff2d54b4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..aa5f5f565
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From a06e4ca5d41760d2466dbd521a12057caefbca8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..ff9c66883
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From 7eab49cf957e0acb374cf847b2a29d4ea6f4a984 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..2960e7b65
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From a43143900dbc60bddad125e0cab3e2a3cbdb6445 Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..185b4ad3e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 7cf03e3379b6ad313378ae1a0f02db91ac4ef177 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..b466102f1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From 3118b73a33edbe1795eca1f4280689448ecb69bc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..b573c613c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From e637e18d1d53bf9b725d64504ef48a475e9f4b98 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..a78f24ad4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 58c9abaaecb194d267c76a91dff7ab2082b4791f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..e63095dfc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 7d6f883c869187e24f5a753233c6c9de4f828302 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..a63a843e8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 849d88f3fe45377086a5895a3cc6734bd9e52e3e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..de765001c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From b02c3ee67e31d6660d252bf59dd0b3693f0c2a0d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..2befaa0bb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 0ff2af237c9cfeb8ded9013451bbdc098378e649 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..0509ace6b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From 2e532b3e0a0bdc69201cf482eaea801f63bcf626 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..6083312a7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 6db1f8a0fe160e2601cc900982ed5298fb78d3ff Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..dcffc905e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 068db3313b7db26b53c652033af2f1f2bc5ced65 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..a5a60097e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From dd6b79aa7160620a2a195339df3f293ea5a71bf5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..32ddd96f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From 1e810d11afcfb1050f44be983cf0caca505238e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..1c519a0b1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From b42bbef5f45ee3e80947f772592b31d8bbb6528c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..a39679e7f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From 7eee31ec573b2960c9a3013c0ee70c2795338a77 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..191375c2d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From 7136b955fa8fdcfd2e66fc10d8875fabf5034d3d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..7b07e4a18
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 80632f2f48c58a69673991991041f7192f70898b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..0aa893110
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From e57f08d43500ef11200f230ec6c0a51f75183fa9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..07439e0bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 9583c2fa90e958caff51b3b7ca8c176468123fd9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..a8e65a214
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From a3069c1e9885c4284427d5d96039cd7592a27548 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..506a3ac60
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From bb97fca990e33d280f9ced9da31e2274a95b6766 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..f1a6b39fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From fcc1ba9e56f0dd4b0d2ea72395c5cc5ebb4abd01 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..cb8ff99eb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 08e2028d427198289d7c94e14e84a91034bc5437 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..48e3e6985
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 5b42321cfe95d29e2d55ec8f2495be07b4173ca0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..6bc55808a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 788108509add4a74c439636dc80f2c08ab5e73f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..cae9289ea
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 50f81852adf0efa2490dbaa95cde8732298a1a83 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..1bd25ffda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From d176622dde4b7f58c892a01cb9338177e938366b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..ef05f337f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 80e1438de5bb34b7f6afa8524b85fea3f14389c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..3a9de09f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 44a0c078becf1c20383f6f4cb2563dd9cabd0108 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..8857b1ca3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 200892321d280e590428c46b524c2c2f22af0baf Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..d34ac05de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From a5e5226f44395d4e889873c5e39a20322166e2c9 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..76549dbf8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From fda4886fef5baf28a7b00aa6b9f1ddb94ce63fc3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..699433e7b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 70ca37d397aac99a09af4d4d32ea0aebae2815d3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..568c78722
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 5dfb9b1c5e97b5e1ee510f8dfeefe0dc859b34f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..5a4875c3b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From b976bfdcfba7bfdc7f5df7694b63a9b7bf683a4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..a20e7d8ae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 77159cc3be1cef3502199c8accce7479cfee3c3b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..c22a74b61
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From 34d5215c26e6adb74c94f53b5b4343ea8d20c411 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..842798387
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 0a14bb8f51202ce1031211e64841927e8418eaac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..ef510000a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From fc4da9aa8772d5a89cb0dd3702a53722d1499ade Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..2af166929
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From e636118154b10526e075c3783a439d58c1f0e0ac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..4691d2dde
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 8e30bf419609719ee1904b8efa8ab124fd8b86b2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..424f0e046
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From 9c1c8a528fb73700074e09c29dfea9045c5e88ee Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..8e2187045
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From 1dcc6043aef5764b6057b466f23b90bac00d6dbe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..fe140e9cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 7f36dd8a9a1d6fd67f8a6765cd56fcab9b10207f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..230de5106
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 2e3bd7377116e63243095f977c816b8624622992 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..57f947927
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From c3279d3990d3391972e906c18d6881c6de464685 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..1785a98b7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 70d9831b52d0db1f50bd313703d02708c8dd63c6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..a8562a5de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From 8fab3e25509d703f0e5bfa99c2ef7f0648d144e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..0479baf96
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From f785f20fb8d5087a7053f6dc667bd28180a222d5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..d6a628334
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From 51cd0b705d9a3cdb90e9b425791da3f4ad47d6a4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..b6803f007
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 4558fe4c9c728d3f7acbc2cfd0912f05afd22a2d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..3ca2089dc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4b9075996e62c4e57eed93041879d74033c82579 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..79f0c74e3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From f39ac70787255ce3a0e2567535c4b90f0e1c8e27 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..922357eda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From c06d8a3ab2d195c933c031d8d25bfee343eb6ba2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..5eda962df
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From 5cef8e80746eb530235456ca4465e90d12c47aed Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..573e7bdc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From ef224b88d487a1c950986f0cf492c751efb96fa4 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..5e94bf8fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From b5ac196d4d5916aaa0d2c1cbbf03d08c6a32a7b0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..43825706e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 8bc30cc7e357839ebe02110c7e8d2000fb92384e Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..1f5f1e6f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 6180cb6894a8a376ae86e8b9634dc3e34985e69f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..9a013b2b1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From af346a832f1e03f30daea3533a885d474dd9f4ca Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..b95a13373
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From 92d59fa871192b6b6413113d85a0fa0f924c00d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+zlijISVVH3?t_u6vnZ7iNH)T=VP=tVQWFIGk92;+nk}&m4DO*F_k~K&JQtGiCZz@sE
+zLUi<GwJ#v`9S%+-C`l;|Fi`a(=e2OhQ%&CX6R7I$WhV0<2N29+O{zpXML<GdIEPq*
+ztSOEoz&K}^M$$l^47e_Ep4s+Jv2t>dcC>c-ilDcSXGS~j`aNEs>cXS~lAE}%MaqPM
+z0F?-k1S8e++aEqPX1kmDE>-u2dcrU3ig@YRJV&1K>mnD%gPIXbKx<#Kh)fnGG>0#n
+zSJC)Il<@xEmQdP}SIjE1Rke);)sPwaCuP#Cyynb4meAU&&QjxBKCLO9N3y{@9z%t2
+z?}2M*I#INor>ol>VqToZEFhM$w8Zybxek1o_>kWtaJ>Z+9;Y(@Bjf`Elfqo`IR-$z
+zE1pU4dHjKN;z<%Uu$GuZ4-|fD7B(cdhp8+QhZz$Ful;Z4*WC6YP>byckl-@VMKgp|
+z6c9ofOQu-_YOxq(!OJxyGFzP{(_Tp<qlxrc5ib12ChOis9Lid-kxZ^0*nd`=$GJI$
+z8NQQ86EHMYG6`}@%F9J&jnM%@>ctv1i5}Z4nAPN8O)_m_<0D-P+71nFd;?ud0}Hd$
+zI_98w$#e&@m24NXe6+`lO{6UhZYF|7som8$No{&XCi2j!4!+a<7r-0?k*|@G)jLC5
+z5?o4rgxa#B3>tJBa1zaFqk$i_m2!2GN~#IxSP*3Iu`d`06_udE9*T8!fiH=B0^eEb
+z)*NolWq6t|?!dqB$Cn)Jptf3$g%cAX;-fjeAJlZ<;e*7d2>CKU8da}~`ZR<yl@omB
+zvA;qQjB=DSBG^pL0QnI0hDl~SoJs8G=^YIOo7vv7Txa<E`|FL>3}gHqqmdr2j-6bI
+ze66)=f``h<2$23#e9y$?B06+b=?3_oQJ&#^B9AM!N+CZ9mwi)+6e+fCB?@%1R%{wJ
+zp7R`|%{zNO!ayj^h1Iz+n|Xdm@(+${xk^8IJ{aeq$gMi`WJNb~GLLzQuE#^KIXKG5
+z?5f_VLp`YMe-DjQO(s9EFGyK9T7t`5x$j*CF7j)pE<G08hfhn5eJ<IE^Wj=2p}?_r
+z#AF3?E6dRMdbk<V3J1U8lq?`E7TYd4dLKYc+`_^1aQ%YD>{b9@@Q?sf@L*^OKfV-+
+zhV0dig~_;V;y#@ldMWdQl<;p1LQ%5lC|Mb$5op1(JmrGvSHHs2+JC`7K-GG86iN5J
+zMk?EN!)0hE-8>^|{d`64W#6aFNl@*V8$lkov06)g-ES`)!mIt&{p!teQ&00aRb*WT
+zVRvBGb<0d^<Np&aX3C2nu?%H_l&JX+f<uUA7vmicvtkPt#sWJQ-0@Y5h7xu`tkGiM
+zu)uzLOLA%hjxx}L)}P)3?om29WO+Po9(;=T9Dq>glc|905|qa(ZWV}AzFTk1i%SRY
+zN7Gu)vqO1$HF*?f9ZQS~#1{I?Aas`@!~%yLq?h0|4<L|DVvmj-R@jgy-w|KFKEUOM
+zr$FrorU~6Q%gUYl<p~E>w40d{lBNH<5PJL&3Cu<#E7`&7`W5FbIa(7Zl%@DO8wZW-
+z(yrV82S*W%G*vVZ>v$f%q10D2P!XPb?f8p4h<_3Zo^ZT*Cy_h;Ps5_Ex@f46o^(q#
+z-}q(h(wwK1IS5mT@lO!T*W3X+SIJHe5yZ(+4)mciwQka>Ln>)aA9ICk_FUI-1pkmF
+zXc<y;C>~M3Xipy0{36j1fTLYgVSSe__f7&QJz1=kjWKRT8Wz4fHtu|kv4!|t2}T;E
+z@et59*s}c3umSKa-wZ4E%NE533F@hMcFd}WuG_=Iik#Y>Z%=l$Dj-9JqWGVnLZI}5
+z+|uXQU-!r7tu{KnZ<m(FGja48`pZrE)$L%*0BI+eUOvowPuZR6yrZfMuV18(S=wL~
+z9HUL#o@&cse7|ZFE2pH4rwB)J%_nQTqW!d`MN{94<|?;XPtIh_mC~8dFXYI$UW1Fj
+zf~=W`xe2o!o9OdNDi~v>am!kRbRore=z}VC#*K;}B0CMo2TwbIZ&{^I=4&3Q<z{ty
+zA6eaY^2vbMWb`?i7m^45&T}a8330BU$bix3vVXgO?N1SWZ$~0Jcjkkcx0GuL#yOmX
+zAD55g5wQ#L?-q%=h4)IXyedkO+yf@GW%j}EKkc)py9LlzmtL6rPw8T6yqq=K_dJNT
+zx7s#JouG-)(|yVlZ*NGHVijxPx(WJ~Fp9mJc9nZ>U*`06bF6yBscMPb8~kcp85|uo
+z$<^L2C&`ZET3#$X+zFu}KV->hs;K78{8+dnds=kCUEy0;psJP7@JZZ54;HRIQL}~0
+zMcnMCoEeZUhVz;TY3!{0cmmV8d&vjg*W;JLVFU)S&yHo>B>Oq*_iz5jS&zpd!u;Tc
+zk0OWKL)zg-bEro|^=fn=%>OEKJXwwZShwzsfnHnkxT<fot0-Zkvj|L;jfO>QKaab_
+zX8jS!{lIJS<0a9Tm*s}x2=1#N;%e!ZL-Vfe6Brr2SKBGO1mq^<aR|2xtoWph!Htl0
+zr@Pfy{Mj!<RW9f0&i3)_n=)i{>a(C2bGVDYTvvR!h@yoJy;oKtfi=bld^z^$ub{=R
+z{6XiXE^YYo>#=`=9wADUY7vXbJT>_DCy!)|M=kHP&kscMDKtF$H0&;$1!OV60JC9u
+zx0?MV51iO;&G1Yyt?+){QCdKn>%UQkya@AIFB>cWt+Tw?_?E@u6nucRgm-yn-r3(_
+zKZgdM11mn%QZM!w7ZkR{3-`~ft(nn2J%)l;hKL{PdMLVEYAGosR(JUcBA@~+!$Y~g
+z_r6T?w;x{e_gE5It=#z*c*dT{-nBNc&trw|h9Zo>wNC3!0j{ua;cPxps21(4@D43j
+z8AkWUoFinzy04u6C*hp92&+dojxennmZ67%>P`}W+qMy2NMEWAZWjkxS3(}-_Fc^I
+zr!h8n8Bc}1)$g!$`@e8`>8OlqZ{n5=&#PUoOegHX7Y8;7C5BcJ=?+GcBrk_ZYpTBF
+z^o6DKkOb|AbiIw?vrT-lp+rb%3O03vGsQX6MW``9yyJz9#4{VqZkK=e)DYGX^irLT
+z89GuZo2E|Tg0ep9n8bY)5or-K6ZI@Z-uXHL)q5QEDBFoAppMcbY{~?yL9NrFJE4BI
+zYYNm6m4t$7c>U|#ZoXXx`_dr*XF5Sh2*WkOK_=Xn1Vc>xIN0^oL&=pzvq!$`;YIjw
+z(Cc?nJNdL|5rtj0xJ@5L!^LP7fb@Q&*-Lazux(2<hDqN|z?~OA!uUF-=aov!+KsE{
+zj}X-enqwE5AV!i4r8$l;vd9_Pa<AAuXlk+)M{MyPOB$75nlEz3B&O~kH5BQa5<H;~
+zT#2tPd6O-4$EO?#nh#T!wi5OV{(Swx>)cFp^F5tjGuV-5+vhi_SE9Y}hu7;cjm!D*
+z?<7DUDmJWC+q0xONcJVS<n5N(j*XNvr=sTq>S3ZF<&M^i+ju?$YjaH9sim{4ZVV9w
+zybMk1WAfw<F8yJ>*S7AaKY+Kxw?%E`1ak%oMSbB0zPQ`vn0m&%t<HIOtXXqE0U_0H
+z!6xl}fkuAoehg(SHX2EG<O3>=LrW{TUDL6sd$LGo(^=gk49sOu5fyS0Ex@p=h@u=Y
+zvOrzruHuX`kzedAC+`zJTjCpS1Tr{j+;6?jW5P288grX(PfYUqcbmBg<;XT)VLOq*
+z>3+@1`+a`{QeU$LCdQ-o7u07rdG3^M7kdYebR`^#s~+^CqtDCGis(X*GT8>25T>nh
+z!G7Zkm48R<nIB2rR}08Lhr9GKPHn59(k`*vxONb%xS{RB)4ed27Hc~aKNk%`=KID;
+zX}+@5mTRaQVv|vCDs0#&8Z<9gAJ2epa0e~f_m@ydFE<YdQ#EN>P1KX=|6Bn_?H4kF
+zzr#%M6~XstP6$@3{#dL7`nH|w94R^+34j6Ulirj3Br+-bWCG1|7BwyJgkrJIdi2Hr
+zoD#u!pC`z1Y&h(s$Uq(9wCsPGo?RjID^60ALn05}Ya(@~!CX8Pa-;9v?FnxftIQPi
+zCmpWA?rO$a{SlX<X-7i&B@%Otgwt~@cK$13S?j=byFc8*VUxajgXch$<SUH98m#vg
+zZaNyxS6IR@0C6$ZN&DEkkfx6P>+HKG0WI9=iQ^6$#06?M{-?8QnKjKw2t~%>baPCb
+z^dzP9?aWwewAknBj?FXprm(U@yphs^iTBD1X)*n2YCFz^UW-)wXOQ4ja;(B_%cn>X
+zZh$F0r<NiD?B4yYH@X4qY6=9DfT*v&$;UG3*WUc+MR5Kn!8jjH&*4upeqAfEa60%Z
+zXy(1BY0r^dTsS+BAg&nWi@AuOv9IgK8>0<>VOJ7U_2H3^S__1kg=(JOOIj=6C)-c(
+z*K<t^X}r)xSQiTCiX#{&waoMRMC)74jzO*TOW=-nwq9$Fna{@i<9i)3n>lv`DZ&~-
+z8SYKTWfTD4Ezsk5ug?5iPFYIblvNEWt|uUhbIJ-`aLW(Nqgrbq7Ps3DZO`4J^vzQI
+z!9dGv65RX9-~2TGkkcVHoCQ5zKeUtX9bOUzn2<ocH0T$U=$y<u4)<(?g-os2o~cWR
+z8qriVzE4c{gY7LJlVX?$M?5W=4#WO}_wTbO{ZRz;Nz+@q%O|8~&b0<>fAV$P4vq3x
+zzhN6&uO=&N>yFu-&*8~rc2199?)Zmonj>%N1kqrl);bx@{t~@a97&+V&A0FN1gq>U
+z^t7?JLC`*20#@Phw~C;Wqyl3dE*<nnRk$xxww^FeCOjvSfciaWnT3N+Gi03-1^Wq*
+z3piR}{b2~ak2e%hir$q+iXg0Pl_z;1-|Mls+S?@tuML_brWF*#`W9ON>)i4#sMi7P
+zZJ$Fp2WD@xIQ{7*K#B*=x5PlBCv4F=BJ4YCU;RM_3O#-Igpzi`DBuZzOk)YB0c-M;
+z?6{ZY?#vG@n{Kn`(7%H}^|7CSdjJia8RAc7FEcUcJfSc$Ku3B;CcVi(F5NkYnY7AY
+zn#Ghw@a4fWi8uPduKeaw=$8dyO4-PB=1kIu&};uj!Fz0ipCP(mnO6QK#1~rjy$S2O
+z&$uP~LMZZGQH!O$dBc|!IA`WnU}j!PyaR=KHC>V+Ex+9m%;ovn_>>oMCmdRWy&Lv;
+zTS?H3`{557!a5>yJqe7%Te?~r+FVR<Xc~D%cHh0%Lu*T}goYINh5Pfd{L`{=+|$+&
+z@>$yR?nZvU;KPzJ*DfrM#H)*J?hTiuQyP#+|AWnV&gwS0;m{HgXI6Q2Xu^!Er8CQS
+z#)bY`%-}wwr~S7E_U{{e;#D-@CjDaqrx4#?4A4Pe%;hW7#{2}^GEeS@oDwGz)_0D}
+zeyT}lS?u;hlA;86JGk44J#22rRs+JoqY#nn$3S!_T~`wg54C7#TQEM3^QZ4KJX2JZ
+z_a~d?Ge-|}O<78)w&!(2KiFQyZAu{*EI)8jDa4x;mav3LQFH4_d2rHg*%v8@Q<{1>
+zdjtZBu9Q$McL;ynMrJRKa<Q6&$XnJ?oXN7wyM6@Z$Ex;z9uwg<7wsvv22%CQi6tdO
+z;7?NB98L7w4>L!6FdY^#GX`wQJ1Dcc<1WH$`FPR;MANA-GzaP>TsPQDHR&ZNM}9(A
+zt6DeA1-yw9sXOkhs%r~G<<J>GQr-_kC7vU1S!BeUG<;m<-UaMr=9MbX7!(lfJ%mVD
+z5zoU%#O-1|_kjp0ykzI(6Hdy7Bswl2I@`qtNf5sprPf;3i~Po*qU_&2K>nj+fKqu*
+zd^_zlw?76%dCHku9;ja)3a>r`7ZY&`v>#A{%7b?<G-OQ&nfv&rs?7$JP_3hVFvg@)
+zT3jz!NOLb&)?2AIGybx?`Oiq6fAdVGwYdv%VbL++K)Q0W;xJZw;*`GTeZUv5#~xIY
+zS0<Uyi1q;99*~RW^+rgc?$$TO5;S7RF3BohCTVr3KexR!)3H3s_ez>u)ZW2M-q{76
+zE4`?VfDaCs%^c`oYw(g_xL<Y$eKu^YERBjOQuMJ)&1ay1SJbLrzY2mwKJwsBXANG@
+zdHlEm>v;1GvAcFos}JoGiQjxdz2Q6mI=6bGG%YxyZ+piuLkjaOa}phiH`Drq(9u_6
+zQ9&UupJ)U08uf3Tobv@-lY!%jCgTQN=v9}MO-zB2lE0=>c6*~0JVNcDz4P1WALBiz
+zWEp798-ns};PUy1u<&^1PW^Dl*J+H8c$@$qk4k*ADZR}I?eQV4x$UFhSF~zXp=6&Q
+zO;xv71>7N;ZRvzngdT?(&vD07TBo7#OBUFrnO2UX#CYU6y}P6Io@%n61>DcwX7exI
+zTLnvb)Nh+T<GQhVbZ>aSWpnJ?_Crpa_D$%F7lc@rJAt;aT83+$Zu|oN2S7&t1CW0{
+zLgk|20RR}O0RRyGF97Lm;_m$41XAgLCy+UsHg-p&2tT)a4J&{t$l}hKFH$}Zu(nQs
+z(WfWz$5IP_4kNWCq9tgjNNB1AKDIumrxUdk8EI=0UWgO8vUAeRax+X+m0N9>7noCA
+zZ%LEtS~@y7mRsdD-mUYbpR6aBR}-dmmEFQ-vo{uauu@n`M}HyD&_p6=;5Hh%p6kAB
+zih7r0e&n~(qWY$~iAyaRwK{KS>zq^DS~kkVS~;Hedf93RWlpuWxQUyGi**HeQcEg6
+zPxQ8g&%FJz*nU!*Y!j92;Us1zi^ip8e4Lz;j@LOXHqz5fSFAdEyA<BEs8PS_`g&K)
+zbg-W94-|5&bW}=J`_yU|Vm1;12{bpp=SVFIv2;<zd=0Ms+#*-WS_Tr8OKfDM5Vepy
+zrQ76Z$|cvR%}lJ;U-A`?8aIovQrr5<k#4oAzVa+_gqc-4kS2;<<ilvgfj2diJVbb0
+zW;2r*=}0Xm{E4d;ZYL^Im_&C<SUaNZ8e*ZZ&w;6WUbfx}7$scC_4mFS6=oo~^s6?8
+z0-~>E4weU2-P_sVcVr&6j*FXvDE8~zXC7wDp!rxoCh&*rfpdskX%MLN_*_5Pk3Y^?
+zlWMRVxA(=a|E{i5Qv`6tj#dbkiCTb=4jYF=EE^R$--<JDVE<M`ZckF0zES+honhkD
+zF-$GVD4BV!i`$@RlyV4|{~j$;s8`ZRZ@7ox)3Cr4PI@Tuq}H=di;qI==$%JxV*OQ#
+zl$+T|U;Nzn9u)fv!#!uH-L*G4Qp0GELoI0P)=59<CH4hCbhL@V#cevi6w`<dyw26Q
+z$i>;P5@K5mqkLFDh~{2#fnB-MTiQ-Bqp`8re`Ro9cAD?nRXv@#1G|uj$62@SC&ea}
+z8w_=R2Wz_=(&x}#G6%ufE(ZZ=5X{IC;o7`58@+Q&v-Ry`0gH)oYmRf%+<a0}U(+RX
+zB6#mV%|(QHvF_*LROUO31V>Yy52=KAkdfgEezU3SA}S$#Yb%>^r40{SC|D5$Z-)6$
+zF~@XGSEUD6&=Qv@kE}SvF8RbQ)LN_|80GAvAa@E>;gHQt#~b+Dz^&f(MG?D(3{v<b
+zu8cOC<bseo^7o@<hFkt&UshPnR9||SK0jGsJYPT5A=@Iu0E}QoG&I7eaYUfh<OF`p
+z3>^Rg4~J-k^2JM$_W(>_J`Y8#!*a9JIVj#Nrwv<9t%m73DX^nT^|g{pE9@spsl*0Z
+zq=S*8P%*nxbMMImu>C?Qkiol%_x%`x<?virBKj3*fZjs(8FG}_gSVP@WAP)ipHrP8
+zK>$*^0bLWdlw)IA%u6Uik~XU7u7iIRgfwuC3TTt_hClRYpUBmadIk9uo9DgevXPPq
+z{0tLI>K(xw4hI`*bSy_u+iCoyo8y5ab);cE44c7vGF?|M6`>TA-hwp5d3CP6^0lKt
+zo83sShkg7E+G#-rZ(P;QFzJ&XnvlL=MX{ESP4M3xLr&P{g@+WeKrh5b(iOEwarDEH
+zmWuB_U{gfOISg;?q~Qb#JgIq(b1`jmWSn~y$SnEe__c|HzoG=dDY8nC=#m!t1dwWU
+z)pDPI>YW$oGHN0Q^3}@(VJVbJe1#Dvzlq}WpzJMUsM3J{lz^<Gk%x%ze`VS?&nV3{
+zCLh)N+vFnj>~ly{p3m|tY%f3+B__z%jDwp~Z-E8`=AaBy3H#bGZUWQ|*N``hAMXp)
+zou*iy*|vX0e8gW3ldUF;jtUwL!{eL<Gpsj2e+z(SCD#1>wA>eVIdpu2{H?IsKJb3W
+zs3c0M+PwuAodk5`S75OtW3*IQk&u-Qjt>{;moygLfpg$aEJ0SXLL}hAP->nNqjqe+
+z7olD@CY*X?LHR@#_PON~{@J4&Z&lf<F5{Av|HP66H<g=SvfQKG$K1Y4$H`BL&rdVl
+zazO!qt{`1rywB`qD$v}wM*cIPw@H0wd;xR!h``*H+J37z*rJkRc3p(q6=>#)4jPsr
+z^deY%k65fo%=hZP-Q+=lYzLgfEy&IwSOzx`WEv0Kr~_ZURDY^UvA!1+2j|yD3^Pw)
+zw3q;fzJFm2FYFZ+;(ji#(b*Wt!^{G(i(7jK>Jl*lvxl8=Y|Ik^QQ{Gfm+y1b748&@
+zvuUQZ=HI4Z{28W=ow=l+l$$xI=QF)map!~m5=g#g8Ue3^G8p?24t_U%^(?OFsu=Ob
+z*~z%QesbQ!9~DKZRg=0iSFY{^@v|vE0>9jYtxvX)#UMb3y-+aHc214O^^Z#y{EDh{
+z5IluYLAxje{C&*Pj0WjZ;PZVwadU%SnN`Fo96!2DCTYvbX%BQdcU*MkukVvK_8>&8
+zuw!m;A-=@WsjDfT)TlI)7E9(yTg<a%`PusTK)V-9ShISvbtb6)3lgbHKdnD%>tv?m
+z2o;11h}W!1Ob_kaE{{BsD96`Ut02XCbjHiqr`}tVh#D?rHh#mcBGEPjzwZk7qx8p`
+zekBI=ZNwF4BwbZ7v)m5KL_?6mQhBp8G^)MN;kHm!(E%aa`hg`5J)9jgCy}Q>QwoJX
+zKBNkz&BDj>;G%_Y`MRwcbe=?-%gJjxQQYTqC+_YJ83hO&#u!3<oYLBW$cBPqvOSAq
+z5ZIWNqJyM<*EEdgd|;KwL`CEX_DC@68Ac;adRuxw*ESeICC&VlaW%?iyu5%8=E7j>
+z=mIvBD+w+fl7JFz8I$n-<;FqaLzO5j11%@sM9t!~5?cVme~6BTp>7j^)`P{X2l(Xi
+z{~_$1qC^X}EYZx9wr$(CZQHi<q;1=_ZQHhO+nJ}&ef6sDt**Y^dyM#vu_8VqV$D5K
+zz46lr?5Y)iJ;{&#P_|dzKNu>e$F*BNS}-9Yb%d!7F*-kAASRo`e)2UHPDqpFTw%fH
+z4Cb(If(*kd9^ouj77T1Qoj22>IH||`?NaXZK3EI&r0kNPnT#(drxe=u>J0L$gxZ{C
+zybzR&^41CoBlT>;E&g))tfj_umo9IBZ*u<~$2QvTxAK%!;x3&}or??%yrzM-aE7ot
+z_ZHU4`R9VdoPx7btQ>aI`Th#Tv5@++lc20x5Sza^Iy>TAEErxL_mB~q&0@w0ZFdk%
+zAtDDZ0mzmW)vh7PKoIK5$@-BU39J*)mSwhD90IVv$OL>%@<@@7EclW)1aY*B`cv`g
+zdZcEbKFD)WcxI`9YuocyIP$gK(X`1EaYZAjyxf~iRy$Bcy8$<?_@Ru%Iu<jIoE0?T
+zUV$U=hn8$00;jWl#?g|a*_Q_2CiSz02>Hsw+R!7K-f2nxIq7lzC&qC+ZWp8#F+<GF
+zhX?8rZ9%_jdd=7CK4{SPHgQZloALS<h-8k9hr+8+5OlObt0uV_8(^v6N&3ZhQ{GC4
+zGstt`52(6&7Tk-t1DDK1bZ%V$n4IwQua=#(UkW4x4XPo+2O9HF@7~i!u{Y(vva7rm
+zvrv!RExL+m@6I^ErFi07R?oSeMVn0asS=n06OR!+Ge((1FcqIq8u|W&|31JdP}t@E
+z%BIEM2w~_GQ`gE*$_QttC7yYZH<QVmL{ce!ts@UWooo{Wk0mo_-lrH{Qq(h`B2htV
+z7?My552(xCOe%>|ZnUTo3*^pSb{kR$MloHLp;z0{LV%2HJYF`6e`Z$~#8L>A0p7Nc
+z9l&ozmXLbE&OV^VSwjvW3DupiOepHbnDJF5r^O<Tx8jZDUkprhya4*}LO+d_`Yp0_
+zVEh2NjIaa3YP^|$m_R7C7)T<g0r$YY;8259Fri2QiQSNpbK*mf=DqC};e9JLC#_hA
+zhkEgw+2Q<E^w(Y=lj{-I?6C-xSfea`2hUm+(Gp#G3}C~E<s!Dj4Jqnou86l$V(AU{
+z#2+q>Xb<qv8nX~PgLd0>K!nr8Ls|?8$lRh3a8OIL3(tHw7u7S)$#(RGt<do#1IQBG
+z_ai&16*+2h_%R<g+Hk^|woU`0-LXZE8Dc9#j!+>Xym2z-hp<>F(+6b_)^%qxf*6GH
+zSfOQiIaQHU@k4daL-A$E4&sIV`*YE7!Mm$bzMff_3TmXf6nOgHIb1I^fT~Vq#-sg8
+z0P-8YHh5He?wjb9KZEb1VyC}-j~jV&(4A=`TeogMEZ)a_clhhsG<~hvZ}n<%s=40~
+zTvk70VAbQ4zrX0Hz(%AO{dqI~av(5e@dj+bBeXK<%N7Jyw>Ggrr6ELZHH>w)*5c_O
+zOzB%?yC){+0de4hx`EVm8WE)_&TGxUzd17S9KN6|_yfci7oU%$&!6)<Ozm9~^S4k{
+z^OnY@r&J2thj!eM=4M2}6m%uWM^CW>YSZsk9FWi%Fk9PQZ_OvkoxaaT<&Mo}MJ=k0
+z$&qC1*t5Ul3wgvlt`+*(#yYlg1{;CiAE`O_!EDd5J97tNvK;R)FPg2O9%7|SNf+cF
+zWxP!^ffVUs2S?}WwN9iNNm7Cy0K|<`oFGX(d7uOJ59@Wy{GdU}%+l^-QzzV_4sbKB
+zj_@b|*v*Ga9jxU5BOJC0c=QSoXcn(Hq{7dq9QE$wn}x@z$S`>r4sUP`6itDXRlP#;
+zI0$Jb=AgE^T!khB%FpxAE7&*M^I*sIEpVRM$<LUO#Vc?U7_j(MiF4s@L#j;CDJ-21
+z&b^#_#Ff4~=Likqn!f`|u(c9Kg?%PcR1@VDwPX0Z&&uYJfZ_&7SOJ?|JCC!#=mA?~
+z#JfIR3WmL4(&CR%qoeh8%~5|*@{2WV%a#u_J?(%-sJx!V$5C~`WC@De+am~6;Yo=*
+zoLx=sS1r@!{?z=qFdz{q7sDwXu;|!DBd*<Ab>rTSrH4$D1l!gNsqMTT9qhT>8u->N
+zgY4GKrFO56zennmcS8lJY~(_sq;3K!=mc~o+edE4AUd`xl}N1Ic6Gt=bI{u|1F*Q9
+z?v<2JJ&6#?OteIAKj?$@I95v+NX{DO<IZ+<H8J6+J+9f$hECH8mB+Eg&iqwE;;W5r
+z<XE)yPTDjU5}!vrM8FnxGlRcL-dgQ#o7sJ@m3<1BT9sdy#yz2m`MzbmD?viq#hvUa
+z+mHHehpTiS?*}QOnWFarn>}p1RnNp|jEmeBDb=$pdBUJmD?qbPlVA@HJLMb$vr|~w
+zbdoh3SLROs(1enF704J4RD6O0Lmv3cZ+RA<9Ha6gb6%Ytrw5GqpF;f3WtmxcyRTyb
+z=kEQc07~UYs$^q~)JPaj@f2DtDgL-Rj=hjF#vSI_Kl!T`+2IdLUHrKRsD@BH<0)Bu
+z5;ouRsZYU@y`cK5_U204$NICM{jvydb8^Wp2eNgu>_!qx%bQ&oJzh(W$~{}SWao~`
+zXUMvHiO0AxFUIO9a#yM*JOlGl(P0$cI9Wc<hf1?;(cJ~Nc1e(!vMluaG46T(ic8`5
+zecGE2<(pB4!W^#VSZ0OsS=IAm3%=Y}i6exzQPv6E65v#rYMb%Ww>4g%%h*i<(Mnjx
+zw+wSM-m_c&`b0Yocon|_5Llp#Wqir?+|LQdjmzR6RXG}W-klF$D$Rk=1moUo(jmId
+zB42ptkp*$wb~2AF2FfJEo_nJ{<OjL`WE~YUi6c`N$l+|aTY0PKwbb|XOyj6)RCnFr
+z&G>L3Z&XP$YaF^V_EB2J1BEA}%d^O-$*WzUfqK_J*B0j??tEW!4$hnl#k<frNmj1V
+zWXUZ0j64u4pQpZ2pbcB95bhg|Dw`?i_}yvpC1dVCRcCpkAWZ1pC*I{&@R9lV%?~&I
+zNAI7o#y=!B{I)o+AAcn_)qf>6F#m)#Ol)2M6VmuM>Y8Y^zeY^#@ZPIxb+C0QS|Xc0
+znBx9<m)kdkEh3AkP{97uQqEJNsLjO5Pyt_`IRql<35x~4HpU1~T}@}~*)yjTRGg@q
+zEnf=iuTreW*_!o>En6Ns7Plmq>oo5rSy!v7+8NvvyjY$hKX<Lkn)H(qOVYJUKx!|+
+zTGW!uFzj%zVp?oDCttoq(M_pa1DfLsZ_BOG<)WIh*@}jJvzM9(uYXB4JszntBkuIy
+zrDii1bp}SdQE^q*19+~P7=wsWu2{N5Vf8+SjC*TAK~7oK1|27KRiv$#*VH74YP9%Q
+zlvH&>HrS8e0C$@2Kl5<?d15=D>=Y2nnF3J%)hwx1BAsB}ywQjat18*}(;5G2Ah4BM
+zKq7&J+RI7;2HJ<-=6lF#bovPsr<#3_=P^Dru&7&tn0dn~fe80IsaDyhCvt%{1!7Ps
+zoQ4W=U0e&f9O0Suaxo1MsXqDjo7cmfgYJuw>+)4bwHsdbd(y4>AjRnXK0Leb5fR;m
+zrB#wFkW86rCUT52A8m&ZYSY3vmt{bmUvGRN0Z&{(2*iH!688CEPMs<d3h#we4ip8g
+zn*3NbMcF|EzHO6&Xf4`}KmRU8W7sXapbA&k4%1~dii#^@TvMF|!*%r{2Kx*eqE*l?
+zd8ZMEt-<&OkTjmJYSah3Kc&ySJtVPoFHVCA{P`JlfE(=(#AjQ+dba=ofW`qHm-pmQ
+zw==7kH1a5Zhz^Jbz>7C5OK+vpkXF;y%?xdAgTd_j;i4h@mXM*V!ox_u)Xg&}%%Mqn
+z6A?SeRYJFMtex&zX_R%VdjIX}o45V$vT?^O1h(y>J|ouhj3Gksr!Cn8mRVZro+l4*
+z){Sx@_PHm;sC*^f7!I-N+&q^|2qP*X=}dq_dz0Yd?hbU1aIhGh3+V?e_JV@H(4}>Y
+zer>=_e9{&!u)(=}n9(tU)t(YyZD^8l{G7?0OLH1N$Kx{mvOnDtfER!VzFtuEi!x>k
+zEb&$4AX{Ld&@4W3>d8gU?Y`tK;329ptv8fTZD7iC#y2W;>qG(8xR9*OP)sdom3D2Q
+z%os-nQR`ml+zg`p$&9v)RLxg-1Ub+&$@n9XN@$LxqUiAy4x}vEPet|Ro)(I^bQ?|T
+zIJs7=SbJve&v%o@tI4xi{l{JPo9j=zTkl6#2HpDcmf9t^k<)JZ8I2!(??jC9u{-a_
+zOOmt)u1Mitk*h2be2ik~Zgg8AcC$7j8B8<o2-}4!_bz`Ok8{9i={}WgP(DkWVpohL
+zL&>2?D~C&7{;s)y;!>CaO5aV}{)#21fmfI5jXQ@fxj5f^4fzRD4}uoKse0pkPGW?!
+z!&FB1+-d=2e4Lm9llwcPaQj<)-X)+}sOw#X&cXMEd8g5@GnSjo_ylU#<Fzda&)B=q
+z`LKy4Y@2Yxn`)fU&B0AOsCk~=Sls+_gXte-oI2l6P>|hoMRujw+Cm?0Z*04gMiEGx
+z{P@1WVJC$jA^bM!MFZ+gS!^3f-BX=NSE2>pwYCo4jt@}_|AgkfGv4TnNornK5t||Q
+zm%Psfv+;_8+7KZGJ(2UAmp**l=05S^HaOAYHL2N1_aB|{6MZOxfRJ;-1-812&<)u&
+zy@7y%G~MktM$8sgbbQXDk(;*c#kPU#d<0C+`(iF2LYtpB+lt-zCwkO<7v`v-ZH7N&
+zN;=9p{pSoXx~P9*F_r=YZ<Uaz5Fo$NS$`c8`t8R&(sLR9(2bIBL9{}ZHsmi3#S)bj
+ziYH$HD6^-2-Cas9XXmf-{Tj#)=M&g>bpJT9gcgxg=)imXahaO)TWtID11zSZ*1M2V
+z9t1}r?4tw@s+}0_a?7_x#wO0l`zf(fI%<)3OwItVKnT$mg^#c^B3k}oZi(rJGADjo
+zdk&pNM~KN+7!|^3WKly^rWiX~FL198*B?}KFTiquMpBZpi1{cqVja4<YiQVVTYAf`
+zqv}4aMTt*Zz*YB(;lHr#pK%qr&V*SYD>)y(FTwSU<L0qjh>8uwcJ1qL?UIZZ+<CXp
+zc*>ai1^3SjB=s*YUM;g1UIq;SP=Np6e927Qj7;qRVN2%ppZAFE{}~!j*7)m7#)|k;
+zqvv12L{^@hesuGft2MIf<ncaNl-PFdybuK*A2*Z%><nP8F@4m#+X!{1;+pU%O$eb)
+zjr#d;1y#CueiXXlt_0zdTOLRkg{nc3twftHZTm7o;XrmRfvmaomLu`i+MJh*n>;6Z
+zOo^u07B~@M6CbYa@%7L$<7JafemuT}n@L9FDdy!^&!Vb4!O^aS29~9Ewyo?&q^*E6
+zp_I#oy8PWApej!DR{$l;Sojza9IUN~eJnwK4r%>V62uQ?;SIm|*4-cl7=Bl`vRA|i
+zS2Hy#lQ@n{nMQA6I)MnMAu4XrCpam2+{2qh8RtgD)EvKVBjjhLyimKj3DPjwkGjFe
+z9LkR@CvlTB<M94Ahw_>@9;8C+V-DhG@&%=I>E*LcuUkd>%eQ?a;<;lkSg-a}HhGg|
+zvMp%F;aFVQ_lY`R>>z<61TGCz?yZsMPcXHl`t_B(B7%3z<-l@g-7sZC)B^4c(q^4r
+z8jW>AdPM?eM30(=Oy-{46m<?G3QE;q*b12xGd@viAB@M|QzeDoN!OUMYA~v~(q#O~
+z-b2TO;WYW7X=r@oEVB16GDN|Rerj2dT;#Beb1q%{l@p{|9QZMDz*;6A!R+E<yU>Jk
+z-bUBJ;|2ZG>OYQLU54LW2^ZSX2ibl%zn0~a_s~}X3c(E2-g&u;9UZr`X&9&#V5E^j
+zWP4hjk8XH8u<vx7Tn$<MhD_tFO!dvF-bkU+4EfdL-z5NM(Ls2g$hMD`HqgPTbfEsm
+z<6{Fe9&3J8=~<W;c!uS@Oea!L609-);D^n?zd$pb1N>5@olM3<P{foGB)+I7TFGpJ
+z`#solr<KO!E4z}XZu?yx&mtM%O}S3umEDS)Rk>R<3qM8yObO1r&}J`Vkj2`ujYgZ7
+z+J{z%aIdO4jMrrag#CyNFe_q`WygF06$F8-W>sx%XqC;_$ss`zqlybVfmjU{uR4ge
+z2W{~XIIp|?8T4%e$TO($UW7}?SjwtBHZA-i1tqj9Zz#DTUb{!lz_?%x?*bR(MbGY?
+zFheJ?eK;5E;DBK<3TJ`GS45Y%!+|_^pY>NFyXT+b$HNV1Oy3?l<TQ#Jv7{H7x%$Va
+zTd-(5VgI>hZCQ1|089>p`3eVFWng}PR~+T&+E4VY@U!$y;`)Bn^9(wS^IPaoe(8bM
+z2cygnb8rk887#q>MQ0}6jt;kHhevN%Xt#&AQ}-`icw00Qkx#r1j?BOZj#3!))P;Fb
+z1?m%!H$c+8zzq9N>Ys>v>}39jrZ^7fV{T3RpiDJ{p$v9*C`v&3FhG=oWPzGV6_^+i
+zKHqbk^njbi13JA@gq?aVI~aecGD2l-{C;SG7kBMmTUc|{xVQLD1cKR|1_SKQ-dR9A
+z7)E&9n@Dc852wsH%UL#t9wf!ei#<6**?Zh=m|i+P@xF&gfTpL<k(I2YFbv2*t~6^o
+zhjN_;H+;>!Y$vi5k}VRxY7P#e>Quv(s_-^et8ju_YHc@AhVr2EQI!CxvKS^XRHVT3
+zv$T{j#AzZeL+t0bP_f7!_U}>oaSW;j=mFs~GQ}z1o_Cy#lrVZXU$?e2AHgIyojC7(
+z7qC!X*W=JxOl$AE3*u{ZPyIcs?0oWH`u5Z9@$R*jVX$Az#)#QoB)r_;6Fynenx`WZ
+zOkX~e6*}H;o^LUYs(3&Cx9s>3$s7DxZbOcFvGA3Xti+h<Sn6l_??!2PV$T9fJTyWV
+zlU5?=1QcwhBbxXEYU#|&qC<hi1exV3r6)Cz^qGA1%L>y3@$~IkFWM`wl#aff{E$g<
+zVR}m~)vR2vivmpA0C}pjOZfFl>Y;gCD?`QA#I$Kul`&X8O#~;#lHc|@bTjVczJBO`
+z)H<NF?&!VJy<o`=i4k&|U2qiLRNU~8(J>kVgFDOkFe(zy*KGQ&_EZ$UR+M~FRw!x=
+zacMeh4OfYZXs}n;w<7ACQ_%bx=aJE}L|cou+Q}AfE+An~3Np1-u!xP63ON_RKpnk9
+zpF*%Ceox8-T&|>nc+H#THcKD7N=ZH|LuD@$vRIb7{~m`IjVv<ppN`CLCL~kG_px#r
+zrg&kX=`J2oP!I#60q7ww?B(fir33}&P1VKURQ#lHT^eDj(R?e*WvgKPJ*HWFPvAm8
+z546^nC?zP5FD@>DG9_L_zTdr-*K}7pTM_>2;^BPF!6Laf!raQcASuV|WYPAXbO^3g
+zJOJV;hD!qyC>V^NK|@t*G-6M`+Mn=D5-#4e>zO5g+U~4&DqhR!>h48Y7euTy#aGMf
+zMv8bF(e`_{V{akQt%QP60dwMb;>cw^iJXn5z`k{sVbBHAC8StT#kNpslz@MTBt^eW
+z60#pw9Sg5#<#+D{osz7D@l1A3PoairmE>?Wr$WS0d4RPL5X`;oyPWu4<P0%Jx3V7T
+zFK7YVpuAE^+6aC^4so+Qy|xJAE5WVhB?QfdU<6bdvt8kAg{<0Et+Joe-PK;*?)EI(
+z==74M+kK4;pEQ+HmYKBbp-zqNT$<sjE3;cZQL1B64xYz+-qa6Zx+VBu7R}_jd64Xs
+z{r<vz{W^?4jrmG~>lb4VdxrV>-$609%^VkK=E3{mv2e&Wm|E5X<3>Ogp6+zV#KUWt
+zT;=SDV?`w&#)aykl)my2wQBm?)3$<eVjGEf43sLgb7PmfhKRmU00=oVV~{ki0qb(+
+zs90*SutF{|Ab$chQ==cmK*XdHUkd*CS1s9Z(GMz5xrgz0WvqnCx7~pdwnz;@^%!OZ
+z>CiNqa%tWo+CEZC=sKWDUUf`MAy!Wwp?Fo~O@?xTjQ;A#e1O%|lGeQG9R_`ED6QwP
+za$+q>ry2aXM)+{(7rtuQ*0*NW7}|-86o^Q4!Osnibw~<g9v?!}(#iu0x+5pS7Fugz
+z!oYOL1t_-A2Tq-htOC5f^b7;I&683@-W|Z*k}S3$u2F6#aE7WN7R(BW8118zpLR&R
+z7!o<-;9#@>kVq7gLD3BBh9N>$938vO=oE~^<9$Hvr^PqLKoJIUambw$cvFjR67X$`
+zx|ZIl{p=UkE>I(QSTD1L)7t6AtFmoZUYn1fy6XB^xo9JekSgHvj9>zcWG>1$WVkcx
+zrcKtZHS6cO&t5@0@MQKl?K+2($c8MtMN}?PRY6<_fkkD3$Ub2)i;Dxu2*DcKmBTY;
+zgMZz=SYLcS(7J~W?cz(t!IWrYm^yXPQqyfx^#IH60plvTX?>v)05UG*_d*`i1(GID
+z#o#Kdj4i{>;7rXzK+Q*A*T}eTVUoIk7{3%jD!C??nzt_j#ujuT5%P^?aIbwFas0++
+zba-XH293&R`7JrW1+|vTP&Vun=*)+`dK%He@M^B*fRs-hB(Hn4MO9HkgEjS(!XAUC
+zgo0KC!O>%{lfQjnXRdTG*9wbPG%d62M+-=EwwE#pZafBO4mT%0I(9tt`>^;{2^D5$
+zU6~0X4$G&XP#Bq67!b$Lu16ugXoDPvSwD_he0S=@kHpsQg3)em{6Rc1J^FyMA^k}h
+z-F$R=uXFP=$5ilr<)kDflc>=W)1oAwdwON5m_Uua(a=0*uJNkRJ$~pKCN)32e(nud
+z6W7GZN`UlIJ7O=H+DX~O9WYO<H{O>(OVv!-K-d8N*Redn3T%u!Cb4eMjoohEnLo~9
+znoFUFJz|Nl!Aspy&HGFr+v~IJGsjW}uznIQzFb@nnoeqe{7IxwDSO$mCxxf-EMH%V
+z*w^|Bljt7&(m2S?X1w&MEqIS!r0=pw`smk^wk<zmTj)+ov_MIoz_=J@jgQ-l!v{dS
+z)gAMfb;ml>S#)jt$!Yi>-f-O>^_=UAlJ-M2i#61)H{NicsNj`Eu_{!YiAGPIh<v}K
+zCeaNnhcsFdWa6wy)Z~l&=MRPFSd2SA&bqLSO9gJ`*^I7aF~uKzlOX1KWE&@RkQ;{d
+zxwZ<n{Ev|5b=0lzR)Sd4a=c_wX)|UPd~uxayJM>uz1AiXH8&kj+nba#+l*Jm!EnXL
+zo*AAeSg<vDV+ZNA?y$rS<p$_Ohza77m(Yz!`lDOj&auXR48<Wpvk>)Ir6zKVM@2CC
+z+0agMihP@_uzN+3vUt*IsI(s$?AKy~m^m-_2Xzo)!Ien6q>pNV*2le{AWM2lbJ(sj
+z#^YDJB12)s#{<<*B%Y;Aw@S4UJrm;SiBoT;+-5}q7{6<>QGV2G%w`y&Db_O^O_%xP
+za>~8&6sK*f37=M>^Wf!QP~8@=;u^j>1(8IP<pMJN_CE>heUc@a8TnIX3h+&j%m?s#
+z;7vd?>^xXyhyP*={SVlzgVlN?90~y7?r+!gFSgJoZYD-9&UTLf+4KB6>`YV3Zj&9=
+zXSQ~)gx|=_#)T`k-_);Q8X8nx8q6H^Qy!RsW}cO-p^T)QeW&2B6Ejz6lCXqpi%&RB
+zeCX*%HdJH1DP6;sGFCT=haT4Du-K1Fy@j6p^FiY8C#IgNVwZ`Q+n+tXL6qoKlg}Rc
+z%My~yVoygL1&tIahgdPR`{%|A$VCL#kK=;_Lb_t7gBy|KXf{{V72k@jSvy)_-qh68
+zuU8=wWV*Q$#2Kyg<ri{Re32!QwZjG|-LGN|CYj?QUEE-I)s+jua5IZ$3v6<C6^;UB
+zEFE{UE`)bkRjp?D26Ess&E`?ok``62_p0b=P-qlOB3wL|;_t)5sdC3g_;v~@d%%vT
+zY-R7y&T{1Mx`<xUd_~TucNpa+>SteNWJGA}rd^0T<)n!BO68iY#20eNbCUABac?6P
+zDkg7Reb#olx3gFyoCuYdde2tGXeE$%+Guw~8JP4x+vKO`1XvCsi^01THWHfaNk06v
+zAE@<>@E%0QE?JS!3d)YP!t?4nHlCT;2MH~)>{UVK7VXsOYMS+kTuP|I)K}*EpL@V}
+zfcF&iBa{iterX%4KWTA*k;(UlmHUCEYxNf_rl1UQ55~!5)$52b7Om5|Do3Fd`AMV8
+z5nuYvlIVGIDF}8KrelEKH@`9WkA#)#+w=6)i<-fP70S=VF}7^C-CW<B2`_Ck90lRH
+z+jzNQxw3DCX$gX~v8-L2C!R%51?pt+8B}4Z+z{iSI91YM9>>rH{fS*v$UEUDPS+*=
+zl-`Yd)S|S8L&c)i&u+KOfMvGqVr!)~Q7MN92CkbuG606TOfKj+J75DVFhtd^f6fM3
+z@>7h38NnBO61HmI$Dn9KSLESWII=v`>oPkq;2$;ETHEwByi82EjAM2|Gk`mZ+|cIp
+zLl|8z`@oowAhPcG;im6QmX7Jt4}w-OgFUF^9pgenyK3wC;$QARKeLF?RwqtX==YIA
+z(lF|^6ZjclWdS*2e-43|psnd3k09l>5&86hYY-`0R#G@ZbUKO4ku9*QO=_b6SyufD
+z%eG-6Pv2_c%6f#h5h4e?%}>pvdpIhqBcK^ja=arJC5yNNYkYzsgzR9y8Y9HA$qwan
+zh>DLD*{;hHu=5+whlLlPwWI~-20|#!i#8N5p5q9pp3k|K>Bt+yP|&Qdsm+DmOY^#{
+zQ&K6VXN0<k&*Mb;Ek8d!*7Wqw=`NVNj&kkc+YG<kGmBPB{T`m8?eG7p#&&R_k#bu*
+zV>8xZ{qSWl1`jM|Zyx85i8_#B+DYY)v6E$(tsy<Y=KZr6<<<d%JzW}tJ;-uZPDKno
+zbWDf|;)Ap$CJx6{Ls14{x;<uBWMuNHdV39txWMBInlTsdNk&UEfXY6TB3J|`&l%QT
+z8fXyW!uDt1`o4BW!rtTbewhK#@f^Z&NZO1NiWh_~@{NT+47HG&QxCd977i|Kg1{9C
+zJZFv&DG(%yx=j^;M5mk(M50oDM%l;@+P?wJzAhj^Gd7O_naqH!qO(Eau<qmu!vecs
+zP?r^i#Jbs7=$Gp^V@j2Fe^{U-JTX!)Iu|viE^+Y|h(NdWf^yj^F>-^gvj#uRlvTbZ
+za@P=Nia5GW>D3aRAaNqCKX2u7!F1Ky$|9F}8q70p={Kd|81Mo3;7}sxFIEaL__`-z
+z@q-@=?rUdBpFnBL*L>1tmF*`ohkCxwF3)`9eo=;I^5sTKB4FRoO}(qv$*6|tr^Z@3
+zuPvzisaw1gZ|THR&LSh%f(jmcRK&Ztp-=fF)ssH&;7l-oA^Pp87?X){3A>?F7P6wP
+zB{9k9;FdaVcWp-=bdBHk@FKb{$guTFgbwN5518u|`9m;(1p;=g_9>9U9;)e-#FWie
+z>Ef<C-Ah+4s@+7hk)=JwNb!^^^GjYG01EJkE#(cyuY6I|8;)QGSL4m?{`vB2lys6u
+zD)Ptt`WQgN^20BZc2;FY1rra5G4XlThO+y~4jOOYV~8N_8@Oc2w`%kPm`GYi5o2H)
+zzJ1_Znan$>w!sQD9S;Fb8SqAH&5{Jf;W%e3NR2JnC2N&sL<MU^#D07BBUPs(n}_gH
+zgh)2m=$y;c+%iWqD%ZoIsFSx<EHBX6o|{EqKKy(ks{VRP+W1S$7IJXNrqwa0fC%Z7
+zsWsP;{-^YWE^@!%;fL*ZJ40<GMCwPtU@=n359Ga6j&iRX&gzbmpU5v)pnofNbVO6#
+zc^AckgVUJG5IOMjiNxqNJB1Bvf2l~Di*-ecB@UaeHi4^ru<$|7^AXF5??f=j#DBS=
+zg2TObBfRGFdH00t{4T4PZjMSBC!v0f$>rQ*F^e_{1)PMqv~vSX#!?56LAq;sk3_^I
+ztIORLLuf=Y?5xdRf|D0;OLGG->uzhZGxb1+xt_Axp6X6cCgKu!w2CI3!3By1);qYX
+z$xM4Qs>UYau{2-`*j+Y15zM6nUl};6r#+#5+^xFCZAepALqPBwDdx)d+R%UhptrX<
+zsgfZQ8dL<;X>wVv1~*D2W-QP$Urq7PGD2ncoA}ikOMT#VL8tw+{g6K>na3?+FaNto
+zHu`dQJU|vycVRiTuy8xv)vG8FVvqwj;W%w^Q(^lh*_E$p({3<Vgqj4exU=|m(B6Q`
+zq-;ulf9l~!C#!MD2fB}ulkzeI&3n>^saX1jZ}tWw1MV@@!H>S4mz(fqBpiqAY_a}6
+z95qEbppYC6&_4-N)BFB+jDM~g*@<{m=hAxV_7}NnAC@J6m<6wD^Nlh^)I%%RZ*1pM
+zb=U@!!LjhWt;j{Zbd%IafuQaLZgQPPt%fw)$YjHd;CuENKsI$6P8v?^dF8S{(~DN)
+z1PDb=w3^RA2=v^Uazwfsp$$#uymK}kWYg0_9JmFW!l(-b3W|UvH;c;xr=X(R593ZL
+z&}oz+#YH*=6oMf0HAT#cZNuN|?Rx0X_*wFmH}Y;A9^OR?u3U9(7l1`Hys12MwJ6H@
+zJL53iEw`@CaEb+y*CDeMYjB}gVArlm^N>VslLR3Z)rYFZ<jjp6h2y?wzxp4%=CGZ1
+zp)Tjh!N_h1ibLRSw7MYE=0<R)1vYb3xrJg!IOCfv=lp1kd2mSU?wH%V4n8dCfFbxC
+z|72ZY?RM}IT=#J4#E!XS{ElSzmk~2nQ^l<TUByg%AeG>9|2^zTxLGGpaUn-kUb!<S
+zqh+rCF!D!hwD=hd2dQWJU|VcKmpef8h#7RU?cwA5@mr^?D-4V>wQ9$aIS3{)PH7L~
+zGX(Tce*AVY?r>&(!0(D<Qg&6+AM_XPtA+~V2k|lP$vAH@Hrk_vNE}SVNVsCAgIUm}
+z2gPy8b%*y8rXC|o)6&dR=okl4l%`ket8V~<Ft-3lHR=!CImE>$`<AmO8ih~XSMncQ
+z-+2B_rn{HQfF}Zx7dD!Ho6tiUMu}ZZuvLz9ZaY!%@l>Zi+bo37VVLb<nU;`C;<Rki
+z2P4%mP?xr|W_NVLjE|-4rH!-(G%s|yUce5urH6UH5hNi2#Pbgv?HlBf42`DT0^vc}
+z-?$^vr&5#6MFa+P$na=5H+=6qq{!nZe)OXX#Jc%2&|j+&2&e{1*cJhDlH9a^(eNi#
+zM~z=Yg?~mr+wa{H*h?;{13rsVWocwT@x)%~6YmA80B6e@uuPt^(z~FZ$Gxa?QV^VC
+zzf0DEZ$?D+_Xg-)Gi?O8psSq2`n$?6(lhW{&B6M_UGB_lEg6Ka*vKCsE-G&vISAyb
+zI;BYinWK?rHAlv09>0<(1MO|W#65;G){Q@&BWvOe_UY!!7uFB8`mxf_`Dgw{o_Grk
+zSXs^!KKpm5x7jftX!SgnfD_D#^$~Lk33bI=B>`0Ir8IZSkU0!2^SHDP0Siz_A|S&;
+zgtsJv@7Qi|*ysx^^1-S11wf&1?Qo9dt8>CgZl;5qh5yk0{ENrfKOXglE_5jWNG1A&
+z`EMR$)^-NQCjYM|eX`nS%qA<m_p6$8R%kO|ZJcfnD*c>D0~GL@H6*hDZa`mw$b_jH
+zqDq41D#gbwkI=;pu+6uj6n0K~R=SX8YIA)~l-AkXWcFw7n<llRm82x25;EoEhVl|^
+z&&_TV>BX${^4Aru3~qEUn+%E8;>cwIQ-b9#Hfp4ZD){MHKY01o#Fg*p$n$i-od#{{
+z{-^707fO1mIi*Ssl@89Cx$F@P%9><AmLn`~E%tS=QWB{UvIS$mr_*oW&-?wso9EBa
+z(9Tu7ogUvUo}He~LK+?S{hY03!nA(4{zPpKl_RVQ;qr~X5&bWBt(9O(m17Xk&c82J
+z=bFYkEe*<KKFn}{Wu#CZN{b9e%ZN9Rs^8I%#>sZp!9_kjFEDlI8pxW8w$v<RHohgu
+zD$?waIu^!CWzIsSg)=clI&jCc%`?IZS%5=6;3h`^j==h|l8iBYl)%dFz_(*Z`|qV^
+zO{3xS!k>Goj~8DHlrm1ycwzW5R{OCG#WKA-GR2PR%lTq)mmcCTxDfsv{m+Lz=qe6_
+z&%YvH`$VlgR7OxO+Ow4OgGCMIY<bWmcQmZ7`bgBQhK^t6m3N7r2%3~hxClp-@cm(2
+zHyyPR-}cLN#M)JSnKvjyiRDH>UrIF`#~0;ilbCa;UnALg)OVEoufhM&d<aV7xE6jM
+zGBYqLjn0clELn?ta-J9Hf7W5{>cQYBLPd&;6OK$_d7ARG4mBOHjD{?bNn1`xHM7i)
+z<x(0+Da_TH5o<i_Uu)kkD{x!EcP@lWj)@4<`ru(CA7GER(3{U$+0`IeYGVZe3DY!?
+zImpBEJkn%1<9=d-m@$IUz9Xr&3<Z(xroZj{kZcDAD1ZdQKDi`Ss3sp_ieX?Qq(#y5
+zI#e4GMMQXLKpSAj)(67FzO-+_tEKGgtcYL&lZj8$kGybCUy*F9)YLoJi!2TJ3nByY
+za_zKLGOi5z|G`+%ZMK1Uh~sD(G&m!38b=U2Nq`XC6w<;b={E|HU$Y*Dy9}p*oY!>X
+zd4fM?=KFD`3a`m(@OidewAGm%=$-Z=>JTz%Sb5n<F;&Ev=uBVsh5eM}_8-3A3q_Lt
+zl$iqwcg?&q4>n|XLTtI^c8x|Z)gP7Gbm!F1Y5w3+&h2x}pHr2=DPukpuN$6_x}VD$
+zb2cwzLB`MRH=rwoutgN1atX6pm)i)*>FgP`D{?;$q%-!;5vadC3fR-uO-P?4rixOk
+zbn)GpHStF2H6`Qgb*`QP_N85bXRZ+A#~df{M-45Pxp=f6`zi^Vj#&#iH?LZpt<aC>
+zf`HmW2w>sPgQhtHplT{XgKyk8n~<}E^GdpMP2=p{Y#x=U%g!Y;$oh?7iX~$x-ODd(
+z_4L#p3Ka=x)%1siC#)88U-CGahUF}v+8lDLmbW_{X9md*UUuq0;QfkN?edRf`aZ)%
+zMgv8V*s9R5DOe!xxMMDYXOxAD)V^e^=1e<G%pvaz%Kmw+8r{|IB=M;eaQNed$63+Y
+zR{!X!tkTfV{co!e6Wffi3=|I+3IT)n$7eV_(YH2o1Ixdq7;<s{yd9rleZ27LH2qqL
+zrj->QPITr^mDsAbr+K72WB$#kKPtAtUU%IKCK@;n(-I~4c7_~XW7!6^$4wtyEZAw^
+z@w@yU1oZ;rM#=zE6yL}X@z;@gdU{D*7Hlm|U<zGfL>==>r9wh#8(`F)eAHZR#xovS
+zl;4_q3cWT4CL1Snpfohu^e{mfl_k}6A-I->-D8HO(rOmLoVgE^m9V}~&Kx@?ebsZV
+zQPN?Bj1ko~U_H&KiplZm2^=ijeo9|#>Yt>PU2se{oiGD?Gr$n6Q4+<v?U4xK;E2V>
+ze|3la$H~2g(vleYH=%3v?|}U$i^;~o;=dBQ{tXl(6C(gQKo2kS>J&kcNuIQY&&77L
+z6<HK7Yc)5OWTAO}O;Zx!`@YAQ!vH$dHI{f4^kCZbI;5Z$IV7^GfQSZWpOBAsPD%|o
+z;Hr$le1h8O{1}OyHvOwxQZ$qzwg5y(UA&YSTQD%y17I!p%=ZkSE!v3G&N9@Zr_9tX
+z)2-e1$&_lR_(u;;?Ct-{`}&WZy?-p}?@Ipb`<Erz8#p?dIQ~c6-anS~e~sJw$2@ya
+zQkf=yXBzoCF#fqU|NlJyd+Of5*`?&Deh5E)Xy8Ap2zO+LaE3sx_ySI$dn6gcbzUOe
+zct2m)H_%whh|E+K@dH_^3ZamBz$q}1_KON@=XX(`S~ofOp%I{RRsAO5P$N|24K!!W
+zSKRR%<dQG$BGI0NK6aa1jjq4`$t(Cr-B=Jc<Ga8B05gBN1^=^d14lC#8x!0A(%t==
+zx}#MU?Ec=$e}T5PbAuF64U1N`T3S@=Yt}sUU(9{D0fGf28-_xOxDvw*0bj4%@rWcO
+zvTS_nNp4>^H=VI!D+wA<YNo5#?T+O;UdPRos%U<_#1dbRke1!MOv}{8^4RKjUhT=&
+zYT9mz%l;TGV8bumRmHofiH=pS1)!VPY5vu$@+!tgtF5<Z?>|y1D0iK0XqI4D*Bd@P
+z6#&~Jc|Hg}D5yalW{S+iPQ9+qUlQtI9#A{*!h!t})q5i!@Y<2&LWm$<LvmmK*Ki4^
+zefSpb+{6f$b%8uw{UJhwtz!Z}V*dS%x9G;VH5ImKldkcDhV8^jASQoZsbtGcqaIG2
+z_v%CfnmGDj{YX==%t1h&^Vd+qLXD(9$cuniF@p0y!PUud*njp2QB61Q0p^dy;}_Al
+zp2wRt=dpGj9+?y((y*#cfRCS6AHo*F@|h2;ivH#d_PO{exJtj+vS#PHgfN{7MG!L(
+zvl?=AZ%XV`8V?BO_K6SKd5Z#i&teL9nF%&TAMGeihV~7i8ePKFM{qgJqL{Nx=A%)?
+zshh==lNPS>-AyN+y&qKcS0r$AP71kHQYPJNAi!-vk~JAR4t;bUqx%DHE3v7p4U7zR
+z1qe<NMR21u>B=nq#7jtd;$N3f5T*rthZ+i@S4AT?+dmwMu<ycs-1!npC#y~hz!$>N
+zf91-e$4~@~naxdcD9yaSt0-%3S)>}1%9EGb6UA-uPGN7I<6BN(wv_Q7X_L#nl4Di5
+zhAz{$tbm^0Is}dPA#g-o?>kyUG>^~*;=?r3v<_D}5%|id2tuf(lpi~0ev+^FS_}`)
+z)htrKVmaAlkApe9pep~+WB(SX-r7i|h&6`0jVI`3#BwLRSM0@i;Js9T+T6scB6v@x
+zsDK~;E4c(&r~RVjYl>8W-R*BKyR`?qBTV<lAeA3oGUP=sl!@3|r-pZq|1cLv2+FAL
+zfr!&CcasK2Xn`ggg2m@3{0Suk+D9Hg3q_~nW+y%2ysEQsyG*a^i?4?l%i+-yW9c_}
+zN|Qr(r5=pY428Roe#_Z}op$6wcIB0M9_H|vA{(JRe0pPK!-R|_9kjQFPxeD~@M2IP
+zm8?NK3Gxm`AsI+8)~xFzk<C_+)doC2%q*p*sEfT$mrBG=IH*LW%&yImw`t>3Px#$t
+z4-#vai|gy<@pXRgJ(R`)>72@<gE@gH2CSCyE=8obkSZ|4N5V(U&<t^?i=24XNMRC!
+zH)Fq2>hBejUU!fhI1gsKMNv~)ms2Z4T4P)Hz6AYA_)H!qW6fb@Xl9FmW>i@0g!1X6
+zG8~~qHp(n?IQj_9g$lYc-=FIrDkyGXkL2#mzyldVTUzdk=OBe@c8L3sTM1h~O5^r<
+zGFe##J>V9-f^c+wp@KYm(+1;tuf^`8NA!Ei|Ln{i=7!Jz!-fyMEUNXFV2LVx5c4%q
+zcddPe#tk-`+1Ii1YPeFY--@>_dwQXGmDX-rjNA}M1~4*TPDak^mP(rz448*{OVQbx
+zs+Y?**_=B^{F+(Jt6xP1o6r)tWMxH2DyZBYrX5&qRc3X#i^TV7RfQvW07<Ly%^;pF
+zF_I0gmO^V1_Uo?^{t-_MO{)%Zmeb{rg6PjvFV2}_q-ATK^!%EHYc`m`P!w)s?=7@H
+z|J#WvY4*B7{I_*!CjtOK{~ss4k)7?o3YGuOftjrNA8f1}J%36jlw)!0h0ha?)>Mhw
+z?5U#+*@R2)JGZ|fk;FsD`S5u(Y>9t-W@&Z7B^^&_^{f;7ZVdHuyiuceu4OKRTq|5g
+zmR=4Fw==TICq0*yOK7ZI=BO6IuUEUDeMeLpww<|_+ulA@-aZcw3KXsOHP_cPIazKq
+zB%57LG)*E!Z<dZO1o!kRwO3gKa*~=|s&~*1-`x~j-fu1SCX);JN({NN<N~aj^i~}c
+zr3&?ipIe(A7`suMEx&*qylHelw$pj$2y+_SBKsGA7&7%8j@~6%lWX#-{g@bqsHyuC
+z6MRogXNN|A_jYw6Za}uGR3k<s5OvnFY^|~rZ3QAs+Paixq1?Uos3hwO+=0uvTsuE9
+zMb>MTk({fxAK<T)pn!pcOAhYCn%v=k9mAc^>SmfgR(FG;i<|({Bz}E4Ypb9L@S%q8
+zD%&KZakslWT<Qqrr~egu9l&cdVn)B6b`~eX?`kyPSo)(BtgK8=d4S@nw}d^NnG)&t
+zdJj5UbHv?Ad-?Hvnq$B^IZgUvpJ@-qVUrq6YNfF?-qfq=7P<K$oai?H!0#tpBxMVN
+z4vST>G{iv~kxg-iX{2xKwrnU0s2jvTKNq^KQAU*R0`EUb9+))xk1eaVI0j!B!i66Q
+zblSkLq?v}O+ibbMFsg&RJ3k;?;)Z8#aO;~bUZ+%a%k+~U`@~*Q>z0^*!hec)g_-^6
+z;3RyX(vU?`$dQxXea=*T;NpgbCaV%qs<NnZdDlwbJ4CwioOQq+B*i`ZP^C@~C|H3n
+zDa2O<ImeY7U1*W9RdDn6=N}=VCYnFib`S_CB?zzJY~p7L${R<cUl(cFsIWw1I#n)y
+zP*{dOV8n-5t$hZlEHJ*!5HOBkUp@OF_=p83VaC2gn2>@bMjXx&iE6%mW$29p)l;3>
+z*>Ghg=h!>HX}CFFMUz{}ySxE1i!dt`B-b_XSmSVa1C>Eg>tTWiv+-WFc5vuFV~#CU
+z{Z*d-s#h)$vY~q}VZymMOMino-lo(b0#W^)gibCL_PI5#S<AwFRlI3<T#>R8!AE>$
+zp_e&AkP6xORit`TmS+LMVRwuM;d-xQglnl{gKV|8^pkT?uJu4fsHS^&va4LEIfHG^
+zL=~M;-31KGo9(chAwv~ENE0z3;fdDrV^+DBRntGQ;+#pdbF;otJ}W;HUtLb6dBn^)
+z*j>f-W)j^x7eG(j{!2Y$mlGIsp#EK(ZDaZrMAC@IB&+KOhn^wWD;l|Bb=X*vU9MO9
+zqbZE>Y}e{-GhbP_5O>Is;qKh9pOK2YG&gd13D374`~fY}UiSkHO#vdadl7W_*R{-F
+z9o>GJl;R;(2BLi+q`Bn%NHh(BZE#7tF$V}4j5Pvp4*;FppnH4L^P0p?f&t6EPU4U<
+zBudZ)K{TA!(wF5XOO|@adf}66Qf*!2Je-WIM`1_?n3yz}8Fn%GNFi=+lfedIDsUq_
+z@p=R?1%OsMW749b73M_I2?J<cEYQqdS+{@RP6?(6S7KAzPDYZZZ`;(B@3`0wKTVKg
+z@+6X(fT6u~t4H&*_r8-@FqxJ5jyRl!D7?o>3mxi_+m-34&{%RFFtv%w^gWpp47+$u
+zdA)pyW!4Fuge~RlN#5?Z<1HLE?Vg-RCmV43(W$#S`iG_FKGeGeAYjjl!+L7%QV{fl
+z?z=GLCvbf3{z~5#;^Bo1tUyQ1zGQ7IPLl4ECzF|uF-r6hI7l{%buIZLrbE_LM1G8Q
+zD0xrU8z)9G?k40x`7v2xCgCp6`8G<7R@6``+HSL6Z`W7G3+Zp-pmga3oUYhSaZ_V(
+zRfdj-g^+?X8&ZEXJSp&O)2H9D0enFfL+SeKauT|A5x^%mz<hj%vtt-Km%C3~hU(k}
+zx(VE0ZC#%F<EI2qDk|#$*pVXGjPd{=EGCQmQmu}>1l*y*7_wz`Vg5rK(-SDdq|=m1
+z0=e_(<ge|*l@aj^j~q&c`AE!uk%4aPevs%O$AXBi>5$)&QJ4=ZFkkBVV2Ao0z;=WZ
+zJ05aP;T$R*AZrDW<v^5jDjK#DQbTJgKrq@WRRKPg&)FY%Nkt0eXtRT&gsV#V6EYAW
+zh0P3wejU_mNOe0_7|FZ(00SBKG|xp_O#=Ig;d!YYpeg}5#_UM-xFrbr9d^A02Lf|p
+zVbLP&)X(uGBZ?qZa9!E)$nM0Q8}R|LHltI@n_Gd%EGJF*jZWMGL$?POW{o3LuCc^P
+ze#p_mk1R8Wj7Tq)MIHt(eeEKOXpv29C-d%mB)~;cbH3%+s$tDeJ)Q@$A1oQj>Wpnz
+zz5V1YG7V&6b3j{%=LxFCY|1OAD<TBL4%H^2Kb`YsfKhy`OGpJrHRggQQyTfu%wooV
+zy<amxb-7NV7Y;LRg#NSkAm5uBjE+zB%RN`}zDpa6%K@ii&^B2@O9mz|vMLJK`fg3T
+z(QX+Z)N;m$=-}JA$Q`~d)fQ0Xll#^QpL=S!Z)=96Qsjg4us-PTX3l%wtBUoGx{TbX
+zGMOtULVSio`@XLXJe)S8Y)$7Yx0pxJ;6}S~;dTJ@01DHPLsL!31rU*>r??3ZPlUnW
+zFHGI{W$U95L0Tqup!#fn<F5sPHut?mqJSLcmlzTDNVleE_KAm!*~GHQUNymuHb-Z3
+z-dXLY`K40dGX8p8ZCD2~K#?)A;<Q<f5=lPVL`mk(NCS$oJee!Ln+N?1OeLPj@*Kpn
+z@(Yx8L##m8d2g^wNdSTP@eF~uqSU~)NT{$j9;7>Q_M>?$q~AuqV*R5egs9^bjYc_m
+zlokwXV$i&ZW?$MpcF(dcv4fN(OrKP(=}b(B$C+_-9ZV0Zl{Lc(lLU^yu|rth@I20|
+z80r#+#A5T(0_f~dq-d_05|8tH+kg6!!sEAiNdNv6U7iUwc>o^mr&qw1+{J?j#)SLK
+zY<60HDN?jJ#m$IiImS6Q615m%73qsP^n<$byL;b91!C)PNy9`^HkLl?ZV<=41miwo
+zZ&{2ntFV6gJe)VMkI<w!hSt${q)YY|FW((<K$SLVNupu#X=aob_fQ2iKSpuVb?9;W
+z=<KG`n`QXvS^7$kECdbjW<ziW`guzptv}pMMs^a+=U4)^mDEUph<R-__$Bg`a<^DW
+zmysP1O4-p0qcj6o3l8k#FBa>9XpQC!=qUtgJN$?Q5Qt)`Q}-b;lGR$V!)K#=)A_2q
+z7|XPJZPgQ`L9M<sb+c?akzne>U&4{VjkVrv#v&J=ZtpE+KtCzR!RxLJkve+LJy=)P
+zQmlIG$<dIOA|<J%^ZuQq`~3cm^L%rcfI(xLmCAgWW%Wijje9|;80TkkV@9|(Obi4!
+zo7_M;(mx*aBh_{;KeQ&W4MEs`vVEv~A57whU;SDe*DK`gc%6*y`}X!TecKyddcM5s
+z80Z`Z&bLQw&AV}1(%YVo)#?5<6gPe5ZX|P9Fne043_)OoB?qz2bnkyN<XIZIuKD$T
+zalNEx@CbwnTyuXlW!jTAX<KS(RYJTEI%(H#b$^4k;p5rB_sQ<`0CBjqbac)k*T-fK
+z+gi?b2gUc9V6{k-AnE*L;VwTN=)GgpRWLqYqO{{JyYw7hrX<l1*xq~!f(OOtWZ#8B
+z$!<rwtRTITdr;+Kx@=iV&#e#<?CL@57<h%nyiaD&`2*$c!<@ODH@tgNQ=rDir@Jjn
+zE9-iKiw?8%tHNT%$#gK+6!iAvA-pQJnGdbh_E1IlM>%RBIpz=Kid4GdV7KUkslMRb
+z1)C!EIb8=bcef)1WQoHd!U~gzZz6QpEZ_9=YW@fmysqDER^D$e5xj&2esd<=JAc08
+z`Q~3iFtls~I6RkkY7L*&*7$kPc-{@g9JWPp*3g(jxZdlTd$pZ(=d62-S`+LJ>uFMd
+zylKaua&sPz4r5v0y@3zmK*yYW8$mYL{Ey5ngR2Ib+Se##zNs@}*I+_8`<;KA(OXm>
+zci-R8oDsqKhEDYQ$OwPjvA0<+^xvhFr<iGC<K*lXOxA|H^JLcB<DXvrrD`&IOu>yD
+zse3jdlR|6lN3j_efQk5~WIs=xkW>q&*oMZc$-x82`4gUh`3LuW9Kv8*pWo=QVv*ED
+zSP!c1gl4)pDiZGsC+Ut@WQLqm>)x%BC8)Y|nxt2<8p`1nKhbynRDw{1eSs&w8D-_e
+zb3zj{V#VfyFkR2C7p^WefWt!gRE)TO1^1-eu>Zl)qZB3lYJF%+NME`>>OpU-Pw6xv
+z879!)CHsXY7AJIWM^F-|b1&pMt4iqMQ5BB2>e)4}LD*}e|Bh<!HCA)RSarYZ{3G3x
+zi%g@pDpRfoT!g6)b{41mNtnhs_k@*R!U=|_suYp+M|G49UCv?Z)p9Z3qB}0<AV?1C
+zPj4uq9;cz`X#5!&TS_KUAKN~a;|Yq?E?V`5)I=RLB-r(_G#m8QBYQYIsK`ezI|-r+
+z8_Bm?rhAa9t?Coae(!(Bsd7oaZP5PuLP@g#0HFPkuIoQww*LnmtXNCyuLK6w=e3sq
+z>RfU{Q94oV_mcI33>}k9>tfMn_PvMk-=1p(c^sg~#-`+t_bdniV1nY7DqM@mFEFsb
+zd@vyP-PL5<p^geTGczomYmtnda>hz$&9YE0on*pO0=|dUojjTO6OA40t+L<I)=OIL
+zJIBS6LH%~&=1tW9i>-5N&jebwb)0mpj&0lO*tYHDi*4JsZQHi(q+@h!ot%ra&tA{7
+ze!{$%b5_-;F<zOSLxxQm+srOIN2X@W)_=hs>XlfbSkG@}<<TC6&c(AcGfd4$?HV@F
+z?b$oy1uBh`%)t278LFuPc*S*9HdEd^mnDg>#>ZyARoXv^l`Q_Pcf^e_pnGUm^OZRp
+zndRi>E?(O<1$akfg!s`&HO^G7gjo{X*z8#q;$b;D5*w$7N3fB@xFx0%plI~LaFg*8
+zJIv!WR&EfXP=hy$>`>6E1shK6-cVp9rS`eDtBP)2-#yB1ornM{g=Hux*1A#!s#TyC
+zceqxDEB-SGS&}Ss&}qJrAFQidc5nZk(3%+W%x#jEpfl??pnr<T0?T4TEMFjcc_d(6
+zQBd|zq*3WzSbR?u82`nybT1uk6K5*K{5DH;hYGFh*7Le3AyZb9f~%8y9!tNm9E8me
+zOp9>r$V<m!*kgbF7?U0%3j`&AB?|Nw=1SqTMOwJWUj*LU-pLvumDg!Kx!KS`@6X-a
+zVL(YB^c!ZqJT?sAr)>I|pdwsdg@vdVtz<Xey-}NlmnKD}i?5#?Z5;{05_{0r{;G;5
+zy1hlhAd!+sUJxowW3;`YiJ76!r2-wyN75Bod;qC^(`^$^fOLmV4ny%SJS>aGk6C`}
+zC%vSyji&z*GBjQzTqItJV$2DU()=TYV2+htt7(Vk#K>(%@3<9l)KG4t^6MCp3*+tD
+zAv~pCTQ-`CEqwRHkPCFXewxOog5i9qLNjixYP3NJHFIeO5r&=J=c>5P&fb>m0x1kl
+zfNLJ~kDHPOeNUH?+$j7t0@2>Xhe}F54xjhyyF2v#Q$za3ZKDPYp7n3QSTLN^VH&|O
+zYkDBZceF>|DJL)F?bP<jC;W4@!0--b4CI-67wY9s9L|iMh(EC>1Rwc4${+7n;qRCg
+zN|>;~b>P<ZO9!LB`-%5_7lfM}90@PPPyIBHB*IG`h7nMD!JxGULQkG7pv@xh&`!yB
+zqGB9_ar+RyrYNy%Q1Rzi`gJg-A)|tZ)oAlivt6xmm#K=j*HyiHx&9~|vjYglY)-B?
+z%<R+ob-EPLdk8!jxq<k(ub91Yxq&0@!t~hoF+)*v1n_GKnQB~tU_idO1k~ogUm!!o
+zm@to03+4j28{_wnkAI*Go+=@SlmSi75cA~shaBl)zctAG&|#x-z9^r;Jl#yBrCt$4
+zmpV2q+sOwZ^iHMSi}|4;3{`cAyIUlNT~(5GbaO;38}<lf7B#TMzn}@gOba<l3M7cg
+zqFGvBJo!d@PtP8!3LiB2a_n?>{6o^z>%d^Ua-yl+XVYLWbIdiv*qq~;VIu_WqOIw}
+zaXDiehz0764h6m52Yq*X+EIJ22zb&Q1k2Tpm{!_qL;48a@v)NJ!^=Q*m8=C%QRFqt
+zA&`^WAlv#W(T!xNc9y{p#gpJa2r>l&Co0hP7I+d(%~0Uf4fv=0B>XWVmi{#6zu*>t
+zNjmV~CKzQQOuW$i;%S}_)#WJ#M`z(2X2qw;a02D{1zE4}<&!U|X$?{JrR>a&7#Xat
+zqMxf3Oi_5+fX;gqTuT=|)$EHdeFFUOb@z7udc;b@-^*WQ{BLhDQ(tf=&xf~|$BjK>
+z+~?NToirg=$Ktm6<$(wFgx~;4Z#-<^24xKQD!3?f%?;c@IC*aw%WwcygfX(c6LD9_
+z*#AaVkc_2SJJ6zxXcx3*CT?Sggs-(lGteFta!v*AHpDt#3>Fkd3<p}hM8Gd26;_N_
+z8pLYZUFj?^?U)kCvM48&SMg-UY^L3`!3~Kjk0=zw1k2&Jfg|bw=wkD%dn$gG^fG87
+z%VadOGF@Dj`F7WT%3fYPa=&oYW-v<i%nK$&7z~o0eke1RcCV)0KMrvML&%<JNp<QZ
+zvqH0~2tv@Xr@wdP?!uTc|In{8%1p&0In2b6)j;EXU;@<~wvoywtJdh00Q?V)`1XcD
+z2J)8wEk!1c#MxrxoSQ+V$)iHdf~%n<PNKF;40GTybUny`H#2D=nlJk7b`lb>QO|TM
+z?MixO1yvqSpQZx4?#LP2@B3KS!16p;w#+i3mJomJp)#XhA6nfgDepzJ;e_lN=#TR|
+z4<`}~#lIhmaNhRr*=-v1Bc=X}1gDoyi)s>iNZJDxM?pFM1xlKu-D+?P90i#8tXWy(
+z*J>ioJ-=N;iup>-d|P$EZqcAOiJPP=e~06<2Sgd=9N-e(+z@0!1(f_-KKzGKt%)_n
+zNs{yn;umTXwS@Gd4O=3^N1Ox!1(#rqAJ~wUP!I~gU>$%&0-O%5aWjHQGgtzX5mwZd
+znU0l)a;%9zS_wZ#$IZSjIqI?OKF{G37Jf`BCRPs3(2(6@s9nGb7X~Tc?u`22l51Et
+zXy1*~=H{jTwt(7AT|)VQ(N)*ecQ7J5Y=D9&lK@2ItUr?DTLkT;-OYhSdhXC(Ea3e4
+zC1uPR4J3|AIU8kkelUvd(GP17=`S5tSdrr*uW@u%w}F;&<s9JK8fsV$RaEYU$_EOK
+z#d3CQmvTx^DZ^14{)LlBljJx3F-`b~(7O7%*v1*eUeh|bH|S=(#)E_PwKvv4Q+Qet
+ze&0#Smw|Z9TS|)fQmNA^-{r{0v{sbuHk4`|8u$J{AfDW(kT~%gyry<PN*_^$f$u=a
+z;2%39z`g+`$H~V%s&e}0{5nhb1)J*_a^~cuNLwwPhm#51n&dhSSyh|FGtPwdaY)V-
+zU!!EJcy65nZtJ#GyC~HS8uZ2^{KCQ*#vy^v8z&uJyPENMrR5m%!3^oa3?BV$Qr>kP
+zMD7|9gbAs`wLgd8H5|?yM9iEvAf1&4vE#L0vfF1b&*kbFIIx+12!V&|TQ61PTmC0e
+zF0wpX8Gk{#9P8VT^|7jV)n+~_6Fr5$w+l9W)&n4xDPKR2*?ZF;O!5Zfeep~aSYtX>
+z@5!kDx|}@)!)1R{U5zpovQ3>-uP#+sKukz6)_$}gZv+L>q+~Q|p+(ILcSS__&#Zj4
+ztvSGVsUx6%x_@)X4Oo~5;=I+LxrUqjOi&)*7bPGFrLd{1)d+}4I0CwAwFJIJN0tC|
+z_hqM_zQ)HR;6x(7k5=;P24B?e49L{`mpt-e>~t!tA!mEVM#up_e_>73xZ1G646C*7
+z3d2bL+-cSrt&mX1*RLp(#IU^$B0sA_xWH!>-dZ+C>XdY-gN39gUhWQi`cFGqdU97w
+z1W(1hYQo)>a-<TsC9EEUEsl<G*Sj2~eG3_9*!M8}Bz>JAI=opWUF0c^oa5=3w@Dtd
+zm<14oSV5Gc5LK`+_WZa`+Gx-7qc+9$NY8_I#6N0TZ#Qm;ktvkTFu>?>fZf+L*3y=L
+z9;JD@lK}Y+wXK?8W697}ngx(RtSOH%@Yt-%Lt5&;4vQi{!P1chDfTojDTp&#f$&2n
+z$?~545me8yHX*f{BsQ_25fm3@EqD}Jm@HD7cR8N|jmod!$if^qe<0K<m9YSLO2MW2
+zDIh(GnwngM3Ntnw0(P`>ojI<95XL~E<<BKtq0vr!R$Ip*y2}f~S3S=&4|ULWv@!7o
+z{1vE`z@B&vE?}IIw?;_)U--2FG;(iACwlF~obhIo4#8K@ljP(QQRq%7KM=!3<@a7>
+zN#bA61LbmhxC}fA19U^&o-;F$S2eNXpPxPjo~swyYhN?tK)?O)c8jFP*BgLaKNa&4
+zplGmrEz;z?mUIUhQG}SIW|=&9A~Pa#oWu#5R@g}&gNzmSBr*c8K=qD<G`Zp%%ryKN
+zzn9AlHUTJ?AYn8ox^=2n!Hn>njYuRH_9CGUC9Wgb7;bY;*kja)11B!}naBAnVuk^%
+zkXGPGQSj2OdZrpMWHBDCU@?RrFZ${5Nsx1|0B18#lfNxXiGDClvOsw^O7h&rBb)PQ
+zQJ}KZrAVW{hWrlZZ4hN(s=ZGm!N@&g+z550x(&J{UelNyTqA%(!WrEv3F^SwutxBK
+z@8^|lmAx5!4VUa0u1DTj1pjUa7u#GH`aD&O>Dy#(lU)}R791Y7&5nAaH!!FobJQGn
+zL0g<q)4|>3h$g4XxU%5W&<V9w{dE3%pgZ+fQ!3c90oDqJ@LQ@Mb30l>Dy&Yt@It<G
+zsJ(eM9heY|yS2ReK|7wU-3kOjeXDxJ7jd;fqH2Bb%xiSlr__04ywW!sU@fu!LQP*|
+z%P3>Mt?tq4i4<4~(17$=YpXC7$=z}z5t2GX1A2!9L_*I*O6wM&BAtm(s@)d%pQw4a
+zR-`}sw0_r`<^9Di$hNT1e-?GMU<f)dbW*b~U(Ah?XtHJR{eyqW%Bn#>*Wi)pCQc&r
+zoCzKEV>IE`kz7K|nE-V`Vqj`@olD(LG)}BST4z@fy$cLx<1rJ7)t|(*VDcBht$fRb
+zm&%j;0q2zB1L+TuA&BWqM()fG?`20uY~WlPWpr&ekTFNL0_kP;sB!4RWNeGQ(XA(R
+z7Mn>ufbpj5!Ej78(8cXQnt;|hQ&@OGzjMmP?wXX+=G&eeTuk#+Kj*Y`Ll~RWsnw?J
+zfQRnN^xF_U?hl6Xh!jIy?T^!&ghMrpVuO0WS?AJ8^T3qDm3od9;vXDnF00TIvX5A{
+z*uW~e0Vq-Ju8o{A4NdAlJ>;&${VM3!7nyh5ZUV}lL$KavFa0{**y#Wd_eYq^0H)Q{
+z(Hy+{O+gkcif`>yFoBYUz7!KrbAY`t=)Eva<8U4E?}*9OE$^9z1`s2@u_0e88W|;<
+z5=uFp9zAmS4=j2La<+~VWWB1fABOL5589}4kHV@BBEn$f<~!8Q{Ty+<q03d&mx@Bz
+zL4)w$<741$_)EiM=CA|KktV`SUX1m~#iWu!5md-+vo*IeRs0X?%`0B``kI>)R*i(K
+zEBR@pdY~QQ)WbGHjtyBnA|Wy_oW+=?I9Z~&o71f@N<CYRzsXO$Npqa8sy8<5yC<2C
+zgKrczk${VsntO+Q{ss)I!0zO?6lA6p1z@Z=w?QLg`Xt%W4xFLLPJ57EQ0{x&%3CiU
+zOXSjnr+M7YyY+z-eeURCmhQkLH?6=*ib8Cdz1zBb#1<wH>CMRt$dY^GC1s-a$2ZZx
+zASCQdn*t~-Io1$x_-<*U3=V4|XCV*I|M;lIZ(4B|yIhY^0_5B#7?3j)!uEImq^>bz
+zTgN?o^&X13v6Oc8x$0p?VOTqXU;?h6XPc>@%-*G*{^kDE!j{>#cdTTEU=EYhGOlAd
+zx)X9+&CEvMN&y{AUP7x7CIzmJ%(;uc**8ZTHkWhDQpR7@6FqwyVl`jn+sy_Aru6Ct
+z&q-3@Ek!8gGwm&nDC0(lFV~c=%(;Bs-K7&8cCG(-!by2JWY1@>p3^%1z5v}VaPKY?
+zz1;mT$64l2&V~#29(g?!5YQaTe>l$UtPPw@Z5;srZ^Yp8-%~cS)i)hB$B?{F)E*`f
+zz)+y5-S6v)ldOo97BW<H6_LspxaiPXz(T-?fC2|#h3$KN<Yr;}A`gp@Q>+h|78g?&
+zw@cj}9GKc@gEY!Zc$S-`?^JT+3lt?pzGzrY8sg{m>&6^pKFnCkWkiZ4Hw6<ENK!Uf
+zi2m<Zh*YLD#zt9-xCG|q_Mzs2qGCp;ihDC2s%18LX{8{5qBhDpqlK-#!ij~$pDj?O
+zP}1CwoxWr&H7T{(Qu?_FHCcVqbT8LZV{GWbRf`v+t8n<z(gpZ;E-8E++J|7Iv`uJq
+zQ4s)3Bi&HS*{nrfO)`@wykHC$ubGU#!;v}vXRtRW8EogrV9JdFi$}Kj69b*aC-28x
+zD2wX-!apdZ`}yF*(J;beoY2i*kuQlk;9Sb@%M;#_QZRoh#c@tptJpkW<wh!PmHkHr
+zqQ-PKrqwD8-?C#nLs|s~iKF>9_@x9ixSr6gOLl!-qG-fH(7)2eT_mwiUiLs`2^}~%
+z6~z)%{@UoWDFou3Im7p>C;#QrMWuxZwghGHc~MItd9*bCE@1(ed48$Im5cy0g(DHd
+zE=58eqM2C-8?dDmGE8^G_0wbOyO~Z5M82KLVjo#7qujQaOaNT<=VrUbMo>1AB62I&
+zU}M}~SQCX{N488!<V9y(%BE;djEJzh3&F+W(Zl(@v-qtWTW9CM^GECCZ0_IAi(5ki
+z7+QoSO;W#!FHivMx-Up*Qu_jAERCpQd%=daQT8`sU_8ns-QG2|MXL%o2P&1no)zt;
+za=UAr%)O%O#!<+&>wMplOmQ~Lm-fP6Bl%{NtTMsSAvL%gW)CltMuk!+#08dQE{i+T
+zgjgGNy)f2@yg|vPV1vEK1%iu{#<-pD=x1VO0BH){^eQrDj8h=jM^QYMC*`(k!4%4f
+zo|AswVhF21%n&zwo%~D?o&VZjRANz2t13Uf$YGR=YZVbB^`naxgKD4pi!31us2Aax
+z#@ty^LhvW!t^R}`XV^!#{?z7{Iy2@zsU$D~YwpL6Ss5S8749?=!%$70nL<;;<d&$R
+zP(anjN1Kvccvq0Ce4QJ0?hLC@1r>k#_jQza92*j+sqrn`>V=n%0M4qoGS5{MbhXuX
+zi;4vS;N@&_P70IwDBiJvNMk?~`YSFv;VdlW$}*T~2;!KE5xi160&5+hEUYKr_PYE6
+zNevlExI)NVA##Vhmlx+3*(kfsXk9RD%=Y!$KkK^^bZi8T-mFAZK)6El@*$6OSrA+q
+zI^Ba&me$iK;|hKWPX}N7OGMQ~%bYu$@2nsjnh*y25qW3cd$__ih5i*QM_K1cIvB|q
+zW^gCx%-Y`DUopI4qvOhI6HMzW@8iI}+Zt|FG?rKSkr&hbF-m{j+)Nt3Ia@8;=|s)!
+z(tOq}^i&7hS-01JwVLq9-^a14(YhrV-#OA~?f@RFMntb^<0dGjN53<#XMQ50p8_LT
+zUP0bo9W23=9#XVPLTvDkpypi#<zJcvbVaU<*RK1L(k0Dv4?SO9Z*yRLt(C-$#H)N`
+zkit7>cw3BqJZQ=OxbUPOl1*VmOMqv*#pX|0EomW7#@g{*hHl&rX&2^5+YCf60CTr?
+zt(oUl4nxLOMdoT6+mWT`A#r$URuwKL9B?T)uL10mxNdSs8s7|zVpk8IuOK51qkplX
+zBWwC~(6G3>6YJtv8@%#<6vbTjlm0zCe<cBJop)=^mFwLTbMkWsE-XH7zmAXJ=yIfO
+zO$BxtpW%U{<3%cr`fjYg<NowBY)=t-uQ?AZyqtfw0h%mretSWO`uHA|v??IDZoe%v
+zZF3Yb^O*P9X1!;+jZd~XRU#x`uKGAm-~L1ZlYN3Up=q1o{t3%54pjVHd^i7gZuNn+
+zE)$$d6rDIPr2U;}S-i9JnLS)P69Yt2Llbu}HsZ@ki#)xdko4K-#525@x%b}h5_2Ov
+z78e!QSSx4e;lp>((%cqd_8htSE^--UgsaQRe`XT1`R!Mx5GqIh$=w3x)l+H+40H4}
+zi9P>Z?+Pg@dJiLfn^7ht<dj;=EL18c_h&LWzjxua|M;qM8b6tF&wI2Jy@GG8E7g!b
+zALVGFjojC>5?=FB|0cGOG6;ob988X_AzY_6o>C2ja)LO+zA!`BBWIPlYgDWi%?Spz
+z0R8s{vihNy3;D8AA0~<eYA=!N)Zd;7J=s)>JIYl<VyREprbnL`1|K_=IJb&(A*x2=
+zzuQ3vWLZL>FO5eFw;`Fz@go?ZZE_4=ojumbbHFt#*+o8}MPh4lG`BBjkDK0Tc5ZaO
+zs+Wxi=HvgQz3q<iq%dabYyZ)aFW&I)#$XuQcSH;L8TJfh64air42j)p-5@@^obE7o
+ztDb`5M(@4|$f;Xw-O7_H2=gb-DWI%}UKfalNjWIenGft9qLMX#u!m?sw|VEFy(Z$|
+zF8XBknrohowf}(uPZ<UHN{5l|{mNK<KzU*G@4gHf#0wqM%;6y9e@}lKEj3BOZdnjl
+zpWncX2w&8HYr->{I6Y!)t02t-P=fTriN^b~UnBv=&UMfV$ejtp@V^Cl2yri7B~9S2
+z+*x3lk#m}pbv1oDstTH29`TD_Kq=1O-?%Xu(XcygqYW&`oT=O<j@sn>fxXvVXpD@#
+z`@4+AUMIRD(Zhy^ju;=%JN#m_W9E(3Ub|Px{i-Fd_u1Qcdifhvn@R;tHOfT)>HK1y
+z{hc{#aSPO~U8FTSy5YqeyX)K9r18qm1<H_?)I|TP%tDJCI(3r{ga^YNRTPO^UiZ>E
+zt11lwhM{sh&wTzs5ajs^*Tntz0GKHeK-+`ShNl{i0%=m~)^7e*d%I>eCNyDImaA2B
+zLyv4nyyBa<lmVUR?y&1n&$5gA)tc{y!+A?tlil)Hph6f-wK-Iudlu}sSXnlHT~FY?
+zewzRJB?7<fNKMtYwCQ5!#bjliY~H>(m^JJd*#8$<3V({9HHHy1SWF-wd*%OtZ`e85
+znmHK!PwnIX-)o;{?ahQuj)Wg;PTo;i%3;b{&35bg{FH}xx_#89%L~)zUUeJ+(vRs$
+z9FAV4`0A$DHfJciXkz)o-Z`{V7${trDBhhZeyN2D!LWU+MMkUqNYOadNQ1S2wm~Mn
+z!X$^aBEg*(Wt55<>s-w`qlyNPhXJ>-fbVCi{ZC5sXC+_zA8hB%E#<qHy?(St($xy_
+z$q88%v(^>^CA^q+%OpTUu8w2<v`fn^r7x3=JEhW*c+Z%F$UXb{O+;>{teD1f&!W~B
+z3v+Kc)>>9L`Em_!SplQbQuRht^vP5E>tzP}Ih6lW%iy7ZUT$n~N3gc?+XowQh1?{M
+z8cqAE$$f!!<dXD9SB0cufG2MnRs|sOC`Gqo<;sRknQ)c!O00d;1l+r(9H224SaEgW
+zux>qhu0M*Kx!5rCrN--aqn|k0fo5Cjpf)~Or2U=9k*>>>TR0!)`%MMYuGDNk@s~$!
+zl?q)>ue)fvn~oh>dsf(y%M?5Pgxo$`qF!^k!XkG3wn-DoR>qktr3l~o&TpJo$+?AE
+znJSDp-qcyi#7a}S9)&*q(2NvI!?)zBS+G2~N@RPCDA`?uLXqvbIfs!Ii%J|)YJNpH
+zp+rHYKZ}J_HIw=>)%+=9Z6b}2(L_9~s$8Xq)r&N|EE%L)s@vy7Th-FDy+T<FCD2i0
+z_Q)&ycLP^-nl~kuwHw)euC4J``?{J*=VqjX6tih_p`lzz5wW8r(SC`glvVLx(n=j(
+zU~Z*9omWA*t|W^U{J!hQaIr|0Lzah<r`3gn;tL5x<n3gEPIz3jH4^X@A#TP##BwZ2
+zY^{jwo*zPDcj6!>h88W`70|ek8@Sby_YdQ|eX*o}!gCHnRU8;GHw(FQWX!r{e;ATF
+zRg+BD?CH()x^raZKKHsuqr<1P)<|b_9yTR}?U8U6aw!ubO&FUklXdcZcTFLIMU*PZ
+zQKlqW1s|zV6t3zYZ|5)aF=mEYF};I#Irkc?<a$W}M{8`uk4=uA^09>3*wSg&b;QBj
+z5=CoTJB~3H>mjs|mrQrHOz&R?mDfbpM08EbrH%Y=P6WIa!<qp5w~7{uF)FWn<fe-O
+zq86dZxvDO!NHXp?jCMyY3k|`LIw#|go^-e=P_ic>=_D~gFB*QuQ&ro}^&~bR?KBEk
+zh1E<dhE2eMSirQXa(B!)1wIpQ(BCpExGTlIG7r!smw48O=27?JB~<d1Squ&eV{x=A
+zbgheIPgrY`azF2nabcT`^Xhfu`GNQ0L&8nvPt|lkM-GA4UGlVrn`Mjk)?8t!C{*<q
+zf7b}Ns?RjRH*+GP<M(~Tn8pYDZs66fc?jm5OQA7z^lrj21M=Zj7`s3A^;&o(Bz!9W
+z^%n(Qf0}zSlwHE`2Ns{bFyG~GWvG-uFjC`;1@i{0K7h4+A9%HV)LK#BU4<nHJZebT
+zFK34!8tj`p;JrnDTdLzcw*?g31_+*pum>KJzKU(2J}n0gKwSWdzWmfOFjtS}?F!xz
+zL{YIuep=KD*tR3lSq5w9q9+ie3Tu$y%b2`*8~`p0BFvcsI|2KGG<rG$#1N%#UNfl)
+z^{r6TOh!aLTIOa8UmP;W0ustHw$*_*$A>$oQ5~16hx2-9=YB&LxXlbpoh+cqme)dr
+zlf2s^b{A5eZ_?5%8hkd<%)_WB`MQE;JyrRH^x26%5?28JT*9tPac&_o#y#&`OB1^#
+z)txxo3RBbjDC`fat-1yW7)$&%9sMd3`K|;b94(qyv&|}(IFE5BugI-2KD9#;stFz{
+zL3qA%Txs(gj4x0YUB;123Y%v=;x`gcQ7rM>wZc;9rUZG47}tyXh~P(QtKcLlZj@=e
+zG4Ua~!PLV6>u}IlIkyMsEO)FE&7%7cTS-dSu5?`VJun(81umAlCKc5j(72hmAI0bm
+z9S5tYS(b?jf@+vaqzS!jAT5*BW2i(Q84XQyO6pp|(cX~I9@UadQ^orM7A%b(@sgk;
+z2myHDXcxzeRjmvfaO8u5dY+#{%Z<D%;a9R{BENL2ARnrjwWxyS3$_=O$FT#RZkXE>
+z5I#6cISy$W>l^6VMB&P=FK@Yhml2HtR6pHmT6iAI_;|%p)P2Ja2k4Kp<va-3f1nU9
+zC`))<WX(-*d>e(={V5>Hp03%P#*6G&T6=L|%^j>ftM4C_A&3C%Y26qx8S6*OGE%m^
+z>Sz0z+4BpoFb^-R__&mqfVP{#wN16W7|FfpS1TjM6D=BUT`{bNzrZy)r5uG(XxawM
+zq_LU^M-5msAU;GgLtx8dTL4Wugzx$1z{UA#E>!Uy-=QK6viK(e0ab;urwJ><qI@=3
+zs#Sz+vO@ziRHzgQ$qIs5FJU!oVJEnOcCl22^~VpeZ8_)6&5Vsy1t=*@RdQP7Y;&V-
+zQ@capvEQY!GMx<C3Q(G*psv415Cvs6NOGSWGwBo+uL{p!Us4fH33I;&EJ<%ml8C}`
+zEkTBCK`{l}pBuq(FV-}-k2_|=YgV_AzJ^7_V6a4x{_De+9)Vfpp^*SMzc(z+vf^MQ
+z7<HXIBNI{jZpWvTm=*~!=_I1z3<<9w@wPx<9RcrEOXQC$I<Tec>!a9AH4RSaHCjCl
+zGB_Fkfg&`9+vjkIk(W9!-#pg*SK81VFuc2*H_{?59S&MeUWmK6qJK;iJDzGuL9vjK
+z!abUU(IB2R@~&~$VLqz1Aqjq*@MqD!Y<d8l*z!#npl19wzG{>brf#dmv7Ld0zysH~
+zq2@o=`;^?bsRzjwb*~?9P$$GG5YW~L84oGd@%jba9mV^OsKwUk8^Rb{>s;o8{wGE`
+zHx^MC);`~s^^FGzSa5c=q2(9RUx<H)Lvh%ArFhM->%2TC7Uf#1$>5OuXe=%c%BHLT
+z3{4*I9{=ooe7+)!4!S@YAeo(2{~`gi5ct)V;VlgbPsUFXaPwp!-PidZ{9I{|-owf`
+zosDLchqjHw(K=8dkAe|~E$CeeKQD%?cg;kFg>yj)cg;fp@^69?3xf(7E|PD7z>ynm
+z_UH-w{sn<#|3+yVVh+Z`+Qp{I^|^N}j4*|l^P%RXhuZ&!WX`&}8N}}ob53k@eLvQp
+zka%KXBut9scLJN7bYQZw^<xJ$$GKRH+@_c@*^lhsK>oyk>Dxt<CE7Q_O7Y^gHdvJ4
+zjHkjcLFkdR-|gH)-?Bjl@f!lFMTYn3fGFjmZ^eoJ`XBER6=R!-iXX=`t+o6fwZ;my
+zIO{6~N0Wq^GS=e;j^Itq!mhp&gknu{w=A@~V$yPudY4vd*tnE4bZDGq^UOt~WOJ7S
+z`Q=`dXpuB~i{a8|S8p7p#Bv~m87XgueO>KUHkuldkCw}fiU7{kcct*~B>NY8C5Yto
+z>uoypB$ex|2|Qsu%Nm1bCyDl#Xu^mx*UXq5;Us5DChUqHc#}<0yM;99rf3_oUHUcn
+z=g<%yZsTsRTewX&Vfi0$%E8K@^>6?~^osY0nlL!#s{q${J!dv<jML3_VLa_0?kuG<
+zj6X}hX?Ql4GYfyMvhe<J*_=XCjos__Zoxtfa8SP41S%RI<c_q!$PAw0uZ~v2Gcu6T
+zji&B7la25s@ZDb1=ilw8V4b6L8u&s~cL}P*EDG8z1iw?q7)Lj&|C6Xi3rTAh*w`$R
+zDtoJ<Ho^%#Qeb-z-bJ1cMU&+r5Z83t3cOyO#J2HTfN-VDsuk$-sz$-jk~x7XV$R{;
+zWFY-HDl3G0`y?Cl2rDYB$LUTejoS_+6`#887H>`*_b<P!Fumr_`uxGEqGHOC2uDa6
+zk(*$B_TA@;pH)80weNx|_{-9isWdtZ`RJ)T;LFG@;z<J22X>6TWoU<A2dPrRigTmO
+zmX-mp)@r9xzIr$d5{`X85JOObvD7tl>Sp8~L~urY%)7pTc?TD65@1gKQ?PsL0kU5l
+zy3TW22|$o#lWwG%mxslt-$EM1H}M?M*ecAzLS+2QaFelyY!MzKO?c2;SlG;uV~kuZ
+z1>?}TtEJTtK&E?9LggN_B^ep3K?3#lyqemBNbQni7Fs6I?cmue#qq~@N1+100Uh;~
+z5TUT_WOM!&y~`9kL+t754hz*_3*T>7*&vxtQ)s&%a-!Euq9&_Q<HPkNw+k63k0+OI
+z#$f+kJ`_K`UP8|&DFj|)t;sC0m|x^iJ$?nBff*cRNO`Ym;K?8*^#Jn!Zd&X6zyyDi
+zKz&GsBCm&9@mUi%5-A{c=}u0Y)y(GuMoCdSRcjXFo5ELdkk#DepTd%x;)M39ZUbpl
+z0gbsRzsD<TBehdPbo<C*P93%p$7)Jk7o%K726>ZDVNpEWf0CLus+<_{T!X*-ZwZ(z
+z$ec)=ZD2qYSOuWpas`OWnfqLno{Z<%iJmpYeZhR>Cm_dc(V;e|uAEieV?VsZ4r1Tu
+zIvnvqBhb4=tqRCaktQ*bBy%%3`RQeLB4shMR1!!=1nQSX;Ng$a&<1f!6t2O(b+T8A
+zs}@q{PAhm_T!wF57{R&ttt}Ktj=eoCpTe}Aze=n#v7V`pRJ%OCo>c6B3#ubsOZ~&9
+z3^~4*UiBN&j&p!0d;h(j?E7GH(Zyt?)f)m=xHpb$8`~jp@AJe1<0qfT8QG-vlmIqN
+zp1}9tJ>&|+<e=6R@I>xGCK}5L1_yvXmME<6a2_QwAX%<Y$TUhxYm(%ro0_tbrWOgI
+z`<p9WnCcj^m}zP^ZgJ{@aGD9<Hxk^*wwIUH(sKBF+lk-{BYt7392!eH{`J2b)!=VZ
+z{I(q5UYdDMr+8bx|NeSe-Rs~OnC0<;8XGyvO%xB==-9*=kFDB|N5apUgvH31wm$H5
+zC)Re^R0c94y;XiNX4yk>?yfuI3g=cvt^;c!)rMRkh9%Q}(X)|p)4wEBL|%hUS3B&M
+zLj>r>@qwcnGeN9Aujn#L=z-`jZpBPU*G(>SU2-u0s-1T#%c=ui2MEv2iakcB?q52u
+zn}Pki%ZL)=0u?l1F$r)F3wGw1KA#<=;RVB>mYk>7%ad8+)TIwje=(CnOlOa?O^Ij{
+zij*=P3KCBsmughLxyrg!$~|+mHJf8+$2Dy0>&p{!kV4ywFJH79D3n<uM6kDv{>4ko
+zoR^T3h(M&0Az#Vn0jiVph+G>w+>nyvm@-NK1t$Dxqm9o&@i=49S2iQjCufJ{0b2i1
+zoGN;<H#%GmTW@`kRk<FGi4fv;q4Rk;{xwx<JvZVg#c`#(uz((z0PpS@;hh^da>lC<
+zEL+VnZ88w#$dsjX4d`RxS>-v|V}nScTC3GCEB$9s2JvLNCTU@Xt-?1mqp8K<v#z4p
+zok6q}Ib@@*CxO6fNg73ay!`PCMM&Nkeg!kt@C`gy!J}Y9ZHOO78|`WI!5JeQ3Et=a
+za=Lhp%930sxm|p75Ua(CT4BFno7@7ds@t19u;3Sn6}z|k>l~QBFXv$LXqs2exVEGs
+z!+O~5b!=~xM5vD)!>>&ST)QG;VRrGUvL%k(+D^q9C+wTTHz=h-Zc($tH6x-jt5Lc*
+z^lnJ(mSdl=-<xdqaW*Al89W~jMtXi{ZnffqL79%Y|1`of>$T<s4$?%q7w~VFIv5g3
+z;fde_<mlYxTQ4exrZajDnK&6&&RPoE)WAc=$s7R!dc_Z{9_r~Fj_70?`==2wJ)<LK
+z1+Zn{UbeT2F$@xvf6a;P_6bUAo$O)*KC-5-9)9OjG-J_d2(OfYe7b6Kp;PY2oTX=T
+zJYHqN(6Nfe(}1L!RP;2R2nLt!jQ9NIvPJELhFJj|{EeB+?e(vB=seoQS%B3Y?xH+l
+z(>j?T#7-1Q7*3a3+Rs{A<Wcf6r9jm`4WQ|!1HY?nhSj~XF(->UwMKi?9e%dsV*RbH
+z4ZADT);f$P)ascSQ`6QM0$cr#2%+WDHA&BVhlOz_hr0bU-y+QHl}rS6a7Kp3Euz(B
+zpj6C6gJav9CkF8bT8hPUj%;Z|4QvdTZsZnMTeXY`*rh7+JDw+28z4l2iZFt|5mDL|
+z5l87)XNsyjW&o!4$XFWPIu^zDwUB#w9iX8hVoOJ4#FR8$Ae@q4dx)gXC2U)-$xN@Y
+zUksl1*YT>AqS~UpwKTcNn5GHL<R=Rj@^CE2t!!$yZV|*7;MoGt!!7h>Oqb){qvrIt
+zae<g-?g~#gU#H7ljKBOxAoo%XzI|(^)5B8E8S&(U!~+z1$G0A#U*hOG)Az>}Gux~}
+z@O(bi_G&Yb<~R(S^X-HP$oDKRjr}X6bz;jK><F|Y!PY8-v`2F+<B@N);{yM0`sH<(
+z@Ez83pTO%<QO<c7!iqS#wa*gvz4I3wX4?i=zlj0}BZqs#%^%(#uFja+ZW_dr1ZyO6
+z$1&JUqX5;eA`7Cl`%)5P+`uqq2=0@%i@x+ujNz;JGn+^R9H@j`lP(a)N*p+s*4;07
+zXfX3fs^!z5!@S6oH8`X|vEs<Kz>eQcj2$_?1kguDhg+ZCJcv>r`$x;uvv8&o5La@v
+zKALWQi^WDyq#o{AX6?Frxqa-z?s+pVr&(`db?q;o1Djx@`!sYgMA^>H0*gC1QL0=h
+zO*f8{9oy#x=swLq`-B;eKR*+Ftc<rv8Ighf-3D9N3S41lF<X|_`OFKsp4*$*+%x{?
+zWnm3JUjP)%5jt!?U&ahC<=G-nWC`3<c|Q9&@BYgc>WS`Crr5SIkW^ZplqUsj%vl!8
+z3@A-zN&)ObVlBm1-RawY!tTE8z-x@rC(&O;c;O_vGai)o1np+Z$VC77jy$@`DEorg
+zGWD7SgI*ihl-rAApC?@Gn&cL1oY9qNE_i(DOvffl8Y8!;hoPZcE;v`A%;I29GZbb^
+zMx6Gu$-l*%gl}kkJEA$p24fkpv4K$=cY4~ouL5MRKql#pX@Y?O9!~e1mc0{7-gZCR
+zYnqHQVJ>Xp+!yBh!9n6}8L#d7eKOmva*W3r9TbFQ|K242be8jUhapi_nWU2ePtNQO
+z^!sX*NFUt2pi%l4J$hL?vq^-(x^3`S#@8JY7LN{Sp&Z)-aasF?4>0X^bQhd4FA+*h
+zFQ^0fp0{&fE0L*KjfmXeq2jZzm#>lJ_<G*zeBSrx%lD=Dx;^gQUZ01nCyXdT?9B#e
+zz3i+WRFU?B^t`v}S6#9xmfQF1IA{Xj(-kv1fF&O~Z07b;OgupBzd3>Ii0S1Pfv<tG
+z^qN6_%>{HK%gy`3+0}Yz*i9U7>F#C56`){eW~I%mL4j3THaypWN_{k2BWtP#HTR{Y
+zl_g<_9-}pB7h&{0r#c_@=B0Ys7E~JPC>MaTHA9EAc_7YGY-ZItB0g8xzkI0N*DkA{
+zB>d5K)X(39@X+V5N$y8f)HUxCoA=JqZo?bb_iaxWUQ0#isg=geEA%`R8fFoWxF66K
+z<7M0grVQ!c<Z8RNGoaarP3CDa^huI|no&8~(8!-Y8P6|R7`A&En=@ZOS^XgG)V3QK
+z<b5tIt&9#gTADV}E&2w#`I0Ub`J+0#3r(M6-AtzI`(=@MF(3y;zCB(G^oy3uMwm!=
+zoe@(LG_a2zXH0Nu4@oXdVPwo}=aP2dof`C7NAstAKv(HHK4CL<^jA4z#~1l-rE7g#
+z#*aJRf01>=1b~M$S^I>w5^Z85WiZ|dJ}2Dag>=MT+@w93)I$T<WYKX#xn7bnss?OB
+zzJ?0l)Sw4lAqVd;eKtxNSl-JB=3Z4akH<6lxLmk7fwYansyaVCU+?$Ej1FVs3qZ?z
+zlVR#j8*|TC(xzfPf32bP`1FOuD743-cUQYc6Q1$<QmUx}G?fhWl@`iec2n8&v)Zc0
+z6~a?(^ou9WJGVUdj$Q|jL+AY{-s~3Yrz!R>X(Ne=8=)h=4zG}AfE4}^Fb7m}K3{2~
+z^oCeG5AjMUu;(DnL`7mvGlw~c&5$$Ct4x*Ex0Wzk3RcK3Ug9JFFe7x^bK-_dW*qiS
+z?hkWKRHdipbX5q}o<*Mmynf;TXm*|RIUa4lg}v9KjP%5Gjs-|H=qwtJ8hSyv=`NEG
+zM;e4(P8a{WiSCSn?HZ~Sp%_)BZ}fHVe3KKfid}d6IzQa`MtEXm&nLck>KrJ5t^}Yj
+z1QHpv@iK>;$nr0YJ!TN&;*}0A@Y5&-kYq?I>wYSbe>P*6eNx*P$7XpTW-QyTeH0M}
+zdRAmib8_4(A7`R5HXDfVU7SKi7L&&Dc9PT823V0xw!It53j#s%XV`*(IzWJxR_w%?
+zrlo=an9<I9qV@=E$=j*OTE&g-I&5B>*JC6gNZH9^_%BmW9#w4cq?awAq+eg3_NWoL
+zrh6{u8_RkN;N(X<9u7f2Z!>*(_BGIaaD52SaI+x0u1-kiSbtjh@JF*9!>P4DyP4iv
+z4C3rnq%XYniLmH+ojPP!n3~*DOTP5Nb*Ya2-o}-9+}em*VfFQPpErzakv{Tc0hnIr
+zZEhqZhFjN*+k)HlF;t-@d&1Bd2u0iFy&^hcsn_l2M#MvT<Ywoc+4b*RQ8EbJKTp^;
+zcq3U8h9<IcOnQk@Mx@wI>G6tVn(~O7E8*{mqVNC3ZfX0GRRRvbrx7`UfWDglLsoHc
+zwy`mB_#bdf<$sq|roAs5u~*}-T|a*tHB11Z0wrDYlus@f({di`XekCiST@E)U;sfL
+z3irV>F%ty0b@{$u>AB{fz*4NQBTP!tAg7((pFdyVm?e%TS}V{--W{`SHdl`0-;NiG
+z4V!8$16pd(UN_cyD@MLnnjUTD`81c8$*f%N0XnqOi^gwPnU+=)_D{zL(^Ag^JKdW*
+zD`ca7{{jt-@OZgq=pN=RjT)}FY}Hh{(^<3{DO868bPLroXi85i0WEB<rZTY$qo%pE
+zcTAa?)=IQHnI@vwNKGp&0A}x{mG?z4%+FK(O_%R6FZDD)<=^hAi;5Jn)6Pp8PnG17
+z`wdHlmWx38yVg)$fJ`@~`2~D;)$Zo1iBjpElR~nMTIk0ELh&oV)<VY7B!UR{OwV}a
+zV-4Hi%ZS#;VsAiJ_w?p)b-8=dZDIUCGd8x6x99Eo@I`B?k#)bb(nsPRt<61Gsr~uI
+zt4%XgvqlVaR9$b$YV9M`=(?92w)eK`M>xLeDw-Ad5MW2UHlmrb(q5Itee>UFdge*1
+zd%HFYNqUdYXEtT33ILd8I8-bak9LJKe@)ND#Nyn5^_v?oAntN=dK&iaowy2!uU&nY
+z;3J1EUPrN94f$$ZpJPbO*eT9$xMxRS6#E!i2Fx3Sr1?-*vTd@ydI8KeH0Ie%yC+VE
+ztsAX02{7`x{zP@>wO3D%B+(Ka!&Ywrj!R}LR9YCX0L|8?!NY(Q_Vp?Y3!6rJw$9l|
+zml1xmW(!wmi6Jx7#Y=2Wn|}~>FT})$Yh_bjW4HYrR#CtkC{Net;Hs@GeXrJRpYVrV
+znWC8ifNtHbe-qDMbwQ?bxjgPfph=rLp4(N?>kFfDC2~0vH8j&yojr6D1>HS(hA2?j
+z6%DNTmm#4yxA&}Y3s@Px7XKPE<S07nH8r_#`Lb)r&A+5q8_!ljEK11zau;dyH%4Oi
+zL;K|Lx&%>5ylK#zAi23f4Z$$8f26PoG{+fNs{;i|mNf%IDc9kJp&PWR^3&yrr@EHF
+ze|M~anFLWF`>_y&=?7Cul(Oj(%ju#D7%rH`sJ`ivfVf6$rLC=&b#L94+pMd2)7E7y
+zl0hNk*RG9bq@v9Vi%Bmdr}LoaX{QGY4`VLyh>S(IP_Wp+>S-HjB7&-Uxmg>XL|z|Q
+zC!deB)EsmZ$h2+AHy@QPW7k&?v0C6kO}vo+<qpb5-?`_tdJC5wD$1Zg*aJn=Hp~z(
+zwqY!%LxOo^<XD6mz;?JDpabw+q=y+M*VA7;6{#A!yO;d<z}eGN2Q3+t{z&!9M>GRX
+zT=ZC2tB$K@zWQXq+%12GBVK{)6Q>-sTba4uyCC+jB=g{+OUhW3K!)VRNk&T{W5dhS
+zXM8}wZY~CDrf3Ku<OLFxaHFBrd|@EDW=&<MWc3y(_=gTkrur=f(Afp;^eMa?`X}du
+zo;MAFV<e3hbgRU1)X~VcD$`9<^DZJ+o$5%KCJNSB9&oz_P=i1b`sWpQQ<uG6e5o(k
+zNCr;J_>t9%3`|8N#hPW%Dw%smjHYP;^BO5twj6h$Cg?-n8Jm|5Qe1<fmpvQRsTwM~
+zbC+Sj2f~=nax^k=it{T`<4*)2>Ld*4rWra_P(Ofn$RSIreq%%Yp>T6>_TPkq{A2rc
+z<l>3Gjvvf^O;u<l$#y^w-PqXlunmg_E{|`+7fLv$LOhi-U-0RTjE``-h-*Phz#QBG
+zp__g~72DIapNXxYUGl1)UAw9(ZbR#>PEaJs&qO_qgcd59AE0GUL5}-IYYQZ&(v}7x
+zK@lurgjM~iG7fw?goF5cdiO)MwTqAeCS4Soc(+MqkvYeo`&Y$(Uj$wBUchKkjMxb_
+zcA8m2*lfqUbd0fr$kL>mp!p6`5J!o%px~l}NI<_+Z^oom-52iy&l-$?GiW$pm~i&4
+zsts=B+0=p}@4asMKnn*3Bvgoa1H%U6q={n^qe4fg?lOW$S(RBK_o7hyosA6f>gcfc
+z2^XdKxR#-W4E&-=HK2c}E%ByO*y0mo;NA*VtNvBQZcRo(WJ7-WwUaPIQQXXjgn83n
+zFEdss4@F8O>gd>s7#?zy?H2H5sPuo*hGRHD)~I?JGozDF>IPIipa-D84Y3TT2rIAA
+zQ%;0GnCR-E-nJc_Oa^3WD3#QNll&oMz$~A3RVg$^X1$8i7Mmm_lsbdc$pwf2is0w6
+zc8tLzo#W>q*~tn=fq$|qq?MdYevuYWrOD(VV!>Ea!>DM-FfY=HDxnG9cQJY|MCH+&
+zgNG*+M#VR?fFkvUv9e#B_f!rerPhVgfW*GAa@2T{Xy?YZe5?3iDJvrGPgLF)OkMEq
+zkVt2n(i?A-Hua&rod>Nra9=2ppQtpO4n#2aE#UhPrb^#%^c8IRXD(_4(jv+VsQ4vg
+zWiPO_^g@w~q|9?UYe=;!i?@nC?m<avB!9E8+EXrEy2>;Wl^mF}hT3EPvF?krwxNBd
+zu<#w%CSDAcfv7yz&skFt^-x@?t1bbJZYCB?S_%Jrg+K44;8+|Q$}%wwW}Qpc4|Uz!
+zP7G4c)5lmNRYZlt-2Dq=qmtmH+eyO)E)l@G9^PnSJ~9*V+r5o<<3PY?^rDovhG`*0
+zAG@U=B8K?a1)9H_%e3s%Aea`2fJ|WduYavZ;+-O3qv9*Tt)y&L=iDfJuq27BbscVA
+zQiV=w>dy4mK&2#OT{J@uDx!i5y9>`-TgR><!y#t{8~>DwOv6vJI6%e466mEd9xBVN
+zCUF@@J2V^N$8}*A$V-vRq>epEuuRh)*ZuZj!$9E-vKNvt7`*!=A#w;vbgrw#Woe1#
+z*+<|prb!^k@Jad(@9k6Ugkg+j4Y!5bD_qHf{A``KP#J5vq+9*Efpg)6QKqown@)pZ
+z7PhCe%bzcsJW`~EEqMAwJY85IDVlO6KT@*;K@etFpI>u=WO_&qepv<d)>sxIS4%xH
+zQ}fY63oYy%vgUJIWFyvgxko8?J;>$c|Ld@JtlNuVD>XegAT+@z;2R+4pFH(LHnK2>
+zKHeERQ;u5X-1*%;v65KZ+j&Yzhz4FnKaBEWX^Y@=IYjSInI4iVBJ*AIjP@LPR}<hk
+z|AQox?eB^z?$W;t&j}MDV@zC%QQ#wqVHFh_NLfQPD_|wgfaKntP+fvDP!s8#Ot!K%
+z4?ciiXRW|Ef@xzmL{_sLwgF%_YN`H9B)o!Y0!gXSnDMY609W#oNRL={;=g~W$T3D2
+zP#(PMk4G1lY#0~GPIjN%kp>ZgK)V9UGJL?(7Vk_!wH*CD^t|bxG8^eUKT(gF;w(tq
+zBm*G(vgAwy$w5?q`T2mL3wlOlJFD;;cs12r=w?C~Gvm2P`!B9m4u_07zmnEr&pW2D
+zvmn2?RdYmtI_kd`$rxhA6PeOpY!}cLB`UR6f>K4`ufRjkgp&`-qtf|e$C+@lU7ZU)
+zL=6yPUQv3H`UDkg&C5fWq_Q`C9mGqeGCO{`h=R-&rY?s;0wM2LOROzzj)*%umK;)z
+ztPC$ytXnEaG5H#{$@hcP(Qf-`ImAa7{mFrHxayDHGxlWvD38CXa#VgRcjm&U5>t6E
+z#Ozd9*5cjcZ2C&DUmEn_pL7nGb$Zs(m=DUhyQT$u{2u57ZK8lxqGy3;NPyX~OCVVG
+z{>3k%lWg|`4GtMjx^1^Cd4rNLJIL<5N_o+vYxLU<#@uoFRm<#!yMEB3J_^f;LA1`;
+zwvo-8jrnbt2g!=@x4SVH!uHGK@#;d&cBZxmcZeD<t1s4Du&&=&?({5CrYASEf0o#q
+zsWXFZ85vgLbWNz>`s^0!XDbmli584UI%!)I9h^SQC345_VxRZ#Ao4!Mp4_I1PaJ4=
+zF|q?<tN$1;IK*<uC*>+w5*g?wJ{|WH1Dr8CBoOc12?V4#A<$xdwWOGs@x@7}==3sy
+z_}ETPFr_m1NPGeXF$_J(k<J=(I)HBBH@Cc2Arr^nIj2aV+m4`Zb>*mAYU~IL<)M&f
+z6Wv1fLGL&`Q0|o^ES2aTAHZ2G=21e_S@5IOILW2eS;_pN3$7I^q?`QTy2*t{7S*3A
+zdBmwb5{q>dpBJE%@j{`bi$SUi`6X(~Sf`79MTKI6<R>>OwWe5rMa;w0RDXMT;+r-R
+z`Pb+>R!v=kPMzxROh~^%H2?5)Pr7FOco*G#35Vg5P8539ts$M`!U{(o;>5rDjohyL
+z+rxF2CgyW>_2gVan4^Ef5qGJ!RFM65NqrwJWU@GlDtrKx$c2f>!1ob6f{RP3(vM$0
+zqkY)<Ms=HL{vYs0DB13FV3G<WE16369HN=mm?}{e3VAR-Ad%rFQO`De{u_cuYA;h-
+zw*@+WXyN?3ag?cLF2WOV^ZzJ})R?qO;UbG^Elf-3)|rsx?yA?9n@ER=nknfeP<V7y
+zBNx|yVdC{w9S_-5+?W|6x!2oRGmvUsNgAZeJD`{YTfN<G2YO7gUQZhIV%QSbPiwOr
+zuTL3}Um;YwM9mXgkT|weIA7iHu<6ef6)#~U>o{>i<h$Q|>m=@CM8j}h8-kg|pQ{&$
+z8fStChsH|0Tvy_1tB%M-^ND}t&uH26djq!`uag!rE=X{~p$+8I+1SKE=GRmO{T?JA
+z{OBnR<(<*BH}qe1Cj@aVF4n3f(L91YuboJlZv&+T9d2|NZb1eR<uk@88({%VXz>&x
+z#*2u&FEnPeW-NqHIxp`p-HNA61YsE+9Qf~0493I(glL?Or+y^X5yg;QEUw@mPtGpR
+zkE<34ZTlvBylUtdm%Sn;Crb(Chw)Pl-Uchiz=yIIr~V}nC_+fhL>V?%CaJlDmdzh^
+zf0NZwt{~x;%=av81R)Vbx6rieC)4rZ`R{pTWh9RchF~9o#G@@a%0r)`@-3+*f$iSV
+zP^E+c#^&fx$#VfRA@gPer;vH$XR(UVYMK6z%N2|+%+PS8O{^bh{4!BQE2D<>mAU;J
+zi1e~FxmXyfaNVHjE-zSDl)$38q9oxrgV5wrZr5Rv=@N!db=8O$|A(z}Y7Z=2&v0zp
+zwr$(V#Ky$7ZQHhO+qP}n?)0GDJ?M}4uJx`*w=N=293gBF3^IeB{baj}NH_@6Pt5$_
+z$NZah?ldq`rEK#B84*dXYa#SMXeKeDA_15YFhn`>gb7cak<AC{(vLwkcqj8tgF4Xx
+z>}?XrCAwu|YM$f>p$C_|04b7Q_ER|a?;-O=5LO5mbKP5J#-SrRd95{q|MLkhi8N9N
+z)B$Mn2jT-$_DmVS!650wI=)Ia>5TM~BX4cWrvZDxxvr-Fb~Q4^m&c;&=01f)tHT~2
+z9vLs0JJ6p9fzD{-oHF1gUgdZ?MmVQu`@D;o?w0d`6%71am_^pGV&@S6Uz|I=K-Ye4
+zT@P#+`=o4mmq90$JN6P0h#c9Ge1U>pu-g$RwmJ&%+X0!FR0Qi3mGhFeT1Zk9G+tIW
+zbX#UmgAj3F&xhkwem?J)V|+G#-uLuhw+7A(sf1MCjz1g+q)g91<S=n^JQXoiAuNP5
+z@^@nxSE)GkfcI(rog+2Dc14Z{l6LV;0pvR`!WZ!$?X2LDPUN0Ttqk_k$g!uo&8J5E
+z7+t{B2d0XfbQ5XqEJgWNh}p;y4>(CMlIzu034nsN`2jg5{NqkOaA-3gqiY{(@c;~B
+zNPfiRU|!Ra9Fsm53qeIty3kpRN047ILk2#)9L<5w`2LfTJ+1X*SqWDVzV|h!u<>nA
+zaso8-O8ld~c$OtIWWKfggR}8>Nr8;w7wsoRCr%F;CjC&B$pVQ*1)!leCs&m_gqnlx
+z7#s_;8d+RmqYw)$+{F3EAZfEUh_LB7J~`b4sBwnL5+;7=JiYNvoT+OkPg}FdZcWS*
+zq3XS>0&GPd1p|ZLu<Y<Gu=q6u`>mo#hCs2$`%n3e<ef#gXQY`Xb{86}wVYBu5$(g~
+zeOyLsD4ae%OW(4IK<*P&b9$}?Qh*u1X*AJe2Rh)-?ox<<iM`$l{u1B3OCxaaeSxdG
+zWqo@JmMArAx}(hv(TMTmgc3CiA7+tANwW`uC+W@eWF8EnaH>E3$}+>SnI&1_0`kOR
+z@S&8MGTt>Rs~0zB4<J#VU%$nxVmgfh?w$m{IeV#+BuQdO1lKQvujS1)TEui|G*b(O
+z%-BGbT^7k0zsgyoHa8y2@CfKTM?7Ovnwzy;TA1^wwBfd}0=`6kZvM4q!r&SoDb9%4
+z1V6HX6VJXqY2v;Oew`CJ$IKWrg)Hy!@P2+kshmTx0qIKHCJbZ_LpY&~j%><mQ<1TG
+zreGe&VZK6td3P54`VCTG^$&4;S|_?XNw1|AQY9&<R^q2T-I75@7^t<aTyQrcK}QS6
+zC-q#Yz}*}-a9S-+apk-kIllVvKQ#$<LTX*|Z{uG16y(8nCiv-GpRMfD)%8nW;r<9>
+zadF&3Fi)Ah9N^<**?w#)6)i!=zh=(DJ}qc$&0bb*1B1&8lDnY(XP^BT3CUmpiOugh
+z$dmW_y9anNjgwxa&ypIc`;F&SSuZywp16pMd&55%EcZUzgne(@#x~YsxULV^FEq=J
+z<s};1XONjw^*=D`hai5I6P!4h7cgFXw`ol6rWJ$AoHV7{RrQ|UqHZw-y`|OPrD8oJ
+zB4ixsa{oNI$~O{H*lStI0?D`puF)YeVK2;&j1hD!R=JAt`vXn2a>_&;^X{eP+heGd
+zK{y)ya2+TUg|ACo)z?=LY+=dRhu3>eSz^oXUm;tCUhB%a@zROs&u4+&pK5_p0xqpZ
+z(Zi%Lqii?yCx}Z*YQDI|>~Y(dbXKS@Snkt#ib~wIqyQk*X0N390rOROPM8?dLZ^S9
+z7O@Hn7EZVLxaum{Mcp4=(ID+a<KKOik5%D=6HQ16{$5pygI@@KNnEqo;H0W>>A8*%
+z%b%DN0oGRve{=1Fjn%w@nnBk3y^f$s8~AUhgAJon`N$?^^$w(O=d}c&Da}bjXSjEu
+zGm?W$Q%pwzm4eR$8({f$vI8p$qhCV#fZg4*bTefA+b-J!GNO3ZH^NvMPI;F*<3TtO
+zRut-pI!Q+%7xG-q>IcB$ep|XdC<a>sDFRIqJZS|3sfl*$n)R@%5Fw3RQ~BpG60$s7
+z9o5}$uP)lS#kIh{*CF*`9t5`y*)53%xt*Y4-dP0LU6z1Hb6GmB8SzWjMKBA0$%P?r
+zkXtZKtCOn6q#JYEMLbFYnY~ZLGVN_B1W0OA43y5GeA-dk8^dzbi}Lxa;--mY7o9Y`
+zh;pbnsE9W%9<Y`r`UhoZ5-{_H72<8;n7|U7duL{(=b@(he!#pd6G%HRAy`MyGmqV-
+zwhd^oVL&{_!4rY|Vf^$1KlM{=G19Wgik{5F8~eLq85o$~grmqfiYRJt3sre^*io}w
+zkVQY>Z@v!6d~0!{!gnvYOPlDH3--2(wu^jJZCpX7?eD59s+)l#5LZe#c<0jhSrEE&
+zUX(V3I?6c~l|HE-R0-8MmIAEjKaQxL?NNdmTsk0A3_Y4bLHtHoZQ0q)OL36gpMEao
+zw49Rk$~Ys3iu+dns^UdA{!4#Sd@cE;2*nR3>G^;r9bV0rs0{HSo-%518ZK<Ej(iM}
+z3iB8dN2{?0QsF1Rq8FQd9)%&leP}G`cIo7uL`_2}f%5tlha-N4H2P#kiUA!<a8W5}
+zHxOpCX6OS@%-OGm@tgqEZB#S2YTl8M>_j)7c-L=tY?K%q=P3szSzvJ5y0Rj9yyi2G
+z(fQ7k2?&Mr06aE{s{8N*jnxios^FXaZL+YUabBDl9fTlW%5*pB&S3258+0f0nMrF+
+zbv1qZwTF+$dFH$a*`E2>B4|K1eUSe`6)LG$BKyff-v8JpMSLZN9C&}%R9!-S@<$*P
+z(Lj<G{MNRC2lY<{*s+;3PArN;o~*Wy0vt3Jr9+R=qRFDm3xP!!qeE7u1FVt&8D5Kf
+zQ2B%TIGu$e;znYAi}i24fL%MzTY4b2Y0j-Uh&C2+({szfyg*U5bf_;ZDwz6Mm7qSr
+zc3PLHI*oSx%Q;zCu&y5MDSNo`?S8yd!ptx55<5$||1?Zub#J>&{$S(}i?<`n9?#~W
+z6y#SubiW;hh!6vON@;wTra5R<Eje+2;g5$_Fr4Lx`#b5gQtyWu%{_L)Y@$o6j<o+~
+z)F9dFSPC}vcbi!Zf^=<UY&7JziBlO9X54=2(~khd;HwFLOEO$JJbjfw&O}E1XGTuQ
+z3RKQHnr!Q`%b0%u<X`u}P^sO1!HU2d9DswgsqeJa^H4x@FY27|f+QYbJeWms14~L)
+zjP;GGHM><MSmQ}f4d*QV<V_q@%VH?GSZ2AYwhdcZ8bGrUAUfTav1#I?<d~ru%5crB
+zTHb2iS+IYXhr8WU;Hb?IAxYgxRh)LQU<c(22TrsAeK_6JGEEqeo;^kmVvbHM(l}fy
+zLWAg)(`q*c*m_5DNm*1q96%5WXi}XtP6k=i&av~KYpiKd1H#JW&_jTfBVMzLO_2{m
+z21FVB;l}y*oOl1)`+0^hwyi|;ka32I{Ku*drNqd*_t*eC@|H>d3bS-8Rfq}Gf7ba8
+z#WMKimiWCwe_^+<Xr7lxKKP#!;&%B1|F6LALy`G^`DKjj>NhzUa(2W^3hyZDlnK>&
+z1lu{*a_Ki^_-LO;L<21mhA>h8<L?CG)Df>mq@$;EWe~5waa)sN%7RFe_wkA**D(+6
+zxTxWTqSV7484L%J>Deg;%*h~5NyGN$n{#2@S)RB}Drv_=$B!*wh{yNCF0Cn6?vO@q
+zG>|fU?6|4zMD0RBh|$*Q12z%GZlhr;He(GUC3(Q??6R6gUIe{Dt->QXA6JmAn^xq)
+zzP%|5DK@PlAaGVM!Al>Gx;USPeBye5Wd{ov0$Z$qI;uu+lh~aW$&9;f5~#&AH{XI#
+zG0C;eg+y9Bv$djkjS(pLg5y}S+06?7S~_bfBIIj*v<NkTSkKK>tE%w++mWd220m@I
+zFfmLJ3@!km0VqDDJI3O*<%sIsi&C8yO?!Ww<JppnH+}+faJ<&^BAL1wUQ6#Hzx=CP
+z`u<yUi;ho3^BP+zbBdN~-kpUJwoNV@9jm8hWHh)g$Cv)zwR06|idhOYN`Bb$!?fMZ
+z!5^O~10miQrg>HP45DhQ1BvHCHIX=j!OY97+hPa?pKpnesvfxhY>QZ7z`goG?L9<`
+z-C)7BA}3xCJsx~6)-s*(gt1Q=A7DSkoUD(7WN#o#u#aHq?~fl`k@7rlh1r`18enNG
+zUN?XW@kQI|qF7DlNSA$}fQtkkA&Y#xjb|$83?k0N`sZwH7{lN|Ktnl0L0zz07eI#C
+z?LZz4r<q4YC^rMidUGgD<fv#{NkOIuqV$9a0`QR=rN_9=bV4%38&-vj&Mi&Et@@HJ
+zI#Bb?K(UTf;0muV6EOGK%l4q&dCIig9ng!f&vw_LhJ$>eP}iuW8E(7k&T;9R33u^z
+zaoJ;a3+@k-N^<|>l(iIG?xTOb-o^<mc#K%NUHla0c=lQK!26R!1mnH*@k$E-Gu6hG
+za-ukDs$c_(73+EA0yF+igIi7=$*KB{1N&$*{GWGemsf;+0T2z{5xC*{*v8MKnTjUk
+zv@xO@VXx;5D#su{Rw69_ErxLD15P6Q<SV{Z8s!5W-sK85lBG76^tbrS8WO3nYVCby
+zw9Pihg;74rn5nmAO&SW1dHL1j9zq02?&wD9f{KO1o>9hL{F<KhulOUgMhd7e+Gs|c
+z>)SR=v8xb5X1H-I185?0(kd-^s8p?Kgi)8+<?bF9OIYeQu3D;nb5U0BDG?P@)p6m2
+z&kwpQFcBO8u=mVfpZVK+^Onm1<d1@@zWTNd*9-^17)W8?zRcV{d&nSdn{<yf%W>8u
+zJ&+zuF~evii)^MuOe%WGUS$^q4nr0UEJl*)1gu79?iJ;CJny+{pi_dYiSWAaW6A8@
+zO1EHreIx*;?aXHnu4l`m0c_*v9PH2tGgAptaidBy(Vm29NXFy}4x5Yu*X=-*mR9E-
+ziW`Bpg-L`8wgqD7Q1iHaK3c%mEo-*VCb=9`x0JvGaKKA$jf~DjhgMyD-dSC(cfOlG
+z;uOZ(KK`{FzDCOA-u&KrM<i@OL6pb>*r_sqmUrpA;Sk60AB>ik*c38%Xrkii^}ca+
+z+Em7!@~ulY5d)dOo3+H_Zhg#=Q~^GwM85~;k^@k3Unzwl8(I0~91_-roeKU%zgp%x
+zKIW8j^_6p{F75`A=yEj7*<U2{%bLs4OOqM344EaG$4GG%Szx2aDACI)s^;TM_IpKO
+zJaEI&VB@+}wCiaWyD?wJY7TL8<p&+4Aexy|=?rS9VhBS72e&$v+W1%(ZGh$C=DqNL
+zJ^*2l=(B2Y*lRJFucfp3xs#kOa-}snyaBnZ!!5buB1I?FJ4(#5$P5$pIkXHjx=%t_
+zGpGiFqk?+gJCqi73p+k<Z2a8vN)*-TsvDc=g9b(WWz^{L2u@f)N1A<PC~F#GahWp^
+z#adQ(9yqinTen5KslD6jz2lP@LutscJ+h@XAIL<5c!s)DE=2seU~TzSp5qIG)g_+x
+zUD;t!d<&uo84LTjW|3xc!74o%)09hpAz@Cy#CpL32f+Xv!|JRqs|YwNTDEa8HFE9s
+z_8C|wUMq*VPi^U}STBAHh{FSd$}b@t{JfJr$P$Eq<_Mp-BeOU{ojCSLkk-PR5xKkr
+zI;>ky7q33OHp9kIQq~GKv7*9AQk2YDH=&3UsbK>Ht{F{9B+w7q|3K$<pXE*Ar7n`U
+zDg6~T_Ug+uXnrepW|z#>n<C-!y>=ggHLXe%i9$A_V)9JlsY%45L;Z&{)AMEaSYhpH
+z^1;<)z@PA*v0y)`Gc{^1aIoDFE>T*raaP)1FLvV2-Mq2N?3%LWIJb=!Isw2!DUiz_
+z?tU(<UZH!7l^PG>;GmV?#4nV`xYY)bGisf!7qjPJH*yZn`)f#h1rb@{pUmoo*M?oh
+z>X{L$Ue#f}6^c6%wv%d`ei!nm*Y#7kg0l;Oh>9c%7Zq5Vc@%P@xpe&w8$rD{o<44A
+z3BMD}%X5(jxkIKHj^|wl)0p-8P1=cejxmsi?cHgrB}wKA3;9ygxbdw(Rv7g@mq~lb
+z#c|sDbn6Hf$A3QDg8{BRD|ngb6s=`U`Dc`_cJKi!=t^bd^!l-;auiSDY%?C7d*0@~
+zP2Pm`YJO7%;*EER2DpWzB^p#zGq|COf;3l(Hczk2*1IbAQtjd>%#Pyk@`hMd)!G!X
+zlATHt%LyW^caX<w+OPxu{ZuBUBALuA{5+F0r7WwYd?O|i=VO4}1uc)Z*CC*`cA*S*
+z?x7q&e_r1<r?k>cc<peBQeXi3vs?$sv0P?fYfFkAxve+iMygDiJZQ#tS-s;p2{2Nj
+za3|D_UqTsTpTf;5Bc%9YjZ-EIv|Ir6)yZMJ@o?}o6!K^Mu;7L&LrTT^v7l_?zC~Bj
+zgold3vOYe*0oKx5hhxz4+SJ8rWjnh13vk1*_FVOYGhCDpGV3pX8SZbHr@Wh-b(#TU
+zp`gzuq^54&eN|FJjSW;ap!)IS)lE5UyIHybk*YR~fGGKAQL`>V?dHfQbbODhBM>fb
+ze1$(DgY3-OiR_>bUru4Oj_%^zl}bV*GijJ1Cx&y1XkvBb@cklv?^oj6&P7F+L2T!e
+zqD8^C`p}pF5+~~g+8$=-5!JnH!SG*e8w<n)g~FbF!G_XB_>B>QyAj78Iq{X($pPUS
+z&#$u29qLluTV?fZH8aWY9Z!w<SM5&9Y1y0TrhwLB3`X1Q%bfmYtDZL)7BZW^iUiq}
+z32Byw&#aRazK#GG-u!!h4W6G@QPR;aL;EDiXy?)Y_RXW`tv)?cB{oP#QS+H#IZ&3a
+z*n&tr{b*p_tTpl%(ngj4xaBPm*Q3VjFLQtym$dZ<9_kkUz(Qq-t|a6wUT_J1ap3gq
+ziBS*rSmmaV&9W~c16$((Aa(Y|VXrOxgd1Oh8trxtOM%}`&O-RUm2*rEq_)8sBJTF8
+z;$GY#{AQHOx*QnmgJk(Z$g3xQ$RsVBzc|CK4~N0u^9h`&<x*#)cBwjew9$xynJ{AB
+z_s8Is>j+r{QWA8v^Kk*tC~zptIs-+g8cxEDN1gMp*wo1Z^!jb53G4VL>h@=Kz|k!X
+zEU@CN-kHMVz=p*%j*<-FJAwoln%LEMOaQJ!r&5wbmhtul?HdVC59_u+xoS5L56$vw
+zTF`0vfjF~XdNWVHUl6I81f%75CZuOYEl#q0T0TXbWK2G(ck|<5dYcW_l3=z@wIY>c
+zE`<=rr-#(Yt*_epoaGP<?IzK?VU(;2g4Bf3p}v%Ff}>dHY8qqfGZuVa0dT+pjGo$J
+zzzPNN@q*uiTMzR=KmV>Xx6@9)CN0;@!ZkG|UHhk=nWG;E`t%1@tdHVE^he6MbBWKg
+zEsVgG%Uis`bb@{oHv`WLO@yEIIg{jEjK#j{KWPb%^n@ptsfK<G+#3r*v2RRK_ZSK0
+z4xn6LdT<I)QyOeY{MtEo*T7?W`!`i)l~8wbu$JFX3+tTmFxZ$E!Ta$FK6X>f%%URr
+z6LXgg;N`Zo5$miO_H}1UxZSQ8HEDO<+_!o+ynOvCxK|F=0fVzPvh^!_NJRp8jA(+q
+z;ZbJ+hM7fvKii(w+}HvMk%YxJLpaQ~B^y*RT+SDSfbZnmxJQbuk(Nz35Au;-j#a4+
+z1F^@Mv#uf9>S$~e17MBtWmeDE++sJDUvzI}*i*)-H%&{56U%CBT63bSZ|N&T;Z0(5
+z0i)1%@`qek=Y48Q#aCrZdKzp8Gc@JK4#7a~p=u|lS~5GrE!wt8Y}L?Iu`W6-W>=Fs
+z+7}|=TQ3JkFTSuBp@ilk5qUztms8@NmefT^$Ih1ONjAj+#UMJfBenP^q}Ivb2=Ys1
+zAYwH&CEJY7-0nptHq7b_rhsFyn$q8C4OS;=)KnHvL{qA9o9A-BSkryVZijk&OR46)
+zoF@PD!?yAr+B7@t`k~yZRMwOE6F2hD(+${kbqD$2u|S?gmMxt0dWEk9Dgtzutv#Nr
+z5)Xp_@H`z7r>1tY6fbO=P)(i}^o%tN@!K^?+3|frI}k1_V7C?^ldvD;s*^svlj~~j
+zaTj3sLt^`%YT|SQs9?#y;G6rxyF#PwZfx;Rp*Fv1(cv~gLq3SiKPAu)MnQiB4sb{|
+zKzMdEc2jsWpldQMY3fsyYT_N74v^@$y0=#^(zIWSvRJy>mz;5BRI<q01F7{tnEtN5
+z*u!)cPCp}+bb%edZ{aJ<qg5}?BDmf2Isv_Al7Oy{-7`@@YTPQpTDKpbg>8Hw?<R~S
+zQy;`&wujrIkW6cU?g|bP6}b-9q~sj%nL7;QFK9m_Ln@3lF=~JZRW?jL7-<Yu`+a&6
+z%y=oe>!JP5iH*TZGJ>^VoY~LGFBTW@NnEg;-2qz#kW!C}K;tjO;2PWzcfj!2b?A|H
+zn<VHu88`*5k}ik$3h8kk8T_3`-tsh)CCFNHo^PuyNEO<e*7gj?C|yL5<Ux4Fdx7_r
+z$~iAh7smCcqDjUDg{NFU7!1G+5GPe(;fY(KUx|;bgXCtM_9eYkyjhwf`;&DpVPWe;
+zTW1TP8H{-cNKp;l*6JeS0Izhmm-yyUyI8>wOuyz74eRYx$!bTg_Ph~oLT}*qo@28=
+z=;dLXQ9o%u(O8PSw7BZ<jpNF`r(G&Fp&!Y%N$}*5S-NV18yH|4g`9a~17x4suCQfJ
+zelU!UOv57)=p-N(3L*gS?=1s1WWq%*H(bu2bL`Dh6)Y=N<7?8|1yAfwC<C<7@_=cX
+zm`UO{)RATl7R8k4;oDHXV%cr}jLz_s6GS%3m{mK@cm**vn#Qii(^IhlP2W|#u~7MU
+ztC99R7OQE6M*%@Op)W}ePu1UGH>6XQ3H#uuY(0vQFgD;gzM|P+KeQKyy=_dLxymt*
+z(y3P8b?qvy0FUi*PK`yuJMcZ}CdJ%{Dlr2?RI(TxkX-}EG+Tjaw-i*Z!I%v>wO0IP
+zm&dLr9|)N(L~VxjYO%d)Yx|Fn;_^M~aqr970t*%IutRIR$<2d8nme0g>^jE*VUHds
+zzG7}<#q(jdN$p2u1(PrG4WN>U_sWCn2J(sCI@-Ut#@|f~kJ|ceyOmNC_bGx)xCF~r
+zfvyc|Jx+q{Uh_&xw|#9pOY$%#sB`<(|BU4F;1l0AWsoxCb@FiC(#fe|o+TD42uDR>
+zIWv>4?i_juarNj-2lV2#B>H)|lq$dWD_6P)PLlhoFCnCsZfk{?T<PXI3|F_!>wC4+
+zI3|vytAHHVZuahBTyM1yK7x}R%Zno;E(ivW9j|Dcn9x&q8>90lDCngvRo5IzJV$RZ
+zi9Yc!`If9oyO^vUBipo%;a9UJ+dR?T2Pi^sgB;#USDn2xA+5(I!E(;#&xnq1Y$N9E
+zQXYkP#l@JSBY0~S0%kn<kzDvZ4aAWT<9D1W?@SN%#5TC@^X23fKPma|BDh};Lk8&H
+z(*z7<>zK{IB<R*yIjh5cQa&tBfK5#A+n{anRe>2AcgzkrU|wG5v#?|Qb9<{-bW|kt
+zvrBFRyI)Y>GxNJX$H)D7=UMW={Fpe`{(Jc4qfT$qN{|OFxy_5Yq3Fjep(%1F)3K`6
+zrh;-LE;2J^-?qf)*lLxr-Cx5Zw+R2mZkd+3Nwu5A&Y;v1!jpEM>7j0^tutHv9Ix~1
+z=9u8Y@m!^&K7UAJw=@0fXzHSaEr#R#il4bkXf+zc(@+o~QcG~M-R&~=Fcl`%Z_kI@
+z?G<Ndd$FKrpM^NhUVY-G#Xi*3P>Wlcndwx{lB%E7qE^?UJsZDj9Fcme)ra##9;`*8
+zKeO8*3oY7$?@X0QKLd~|il69$T5OlN4~a|f7N2ufyJh_})A#QOkWT!tljVx@{|>%-
+zhb>Ir#`K+GKSNE&3H>>=IA}@^oRd9BimhIZEg)n1D}Ou&lR_1buzdTeqK$}KEj}F&
+zWL04i)$S7%Q%&JWAxzKxsx1Zo9WkRNqOWzW>zlfh)SGu=dtond7QBh*w2n)(K50p^
+zJ%n7G?iw{m?KPTsyjxtfg=PcJNunN}GBdA%0NB>!Z*+M3#qd7H5pRZp&ha_x>ig>z
+zyEciIBhO6jinQ)k)+aIvM|zm5?I>zWdk@FwRf1gdwy}iF&4+RK+!lC*88_kQ8kW;E
+zlM`S%O4gcTs-8v2YM*A2No7Gvg3yzcf5FpKgTlI~+WZ3r>|Gs2A^h=lrC6(bB0b1r
+zW5S%vb#B*ZMT#c`sI%c+F5GP=Q8ee6$oVUanJrQ#!j`%eImHUI+S=@YI&PXLiJ42$
+z2uyF{3Xnr^oaZPX?uCHL^G`Qp7P)*$g;SKx>rIG%8mCa?8Y^4-HghuYK9)uZ{6<*K
+z!D$7(QwMfGeFm8RS0ezD_i>Yq0yg60X#)1)Elu1dFc+_r6Ge^9)V{;PmceTl3C+`}
+zb$}Wxm}aVp`!p{f<MicV?Y&7yi=bw{=hMp<eJ!2t_xn<_{K<iE`a)xDXeggBvc%J2
+z)ld9u?AspAr^|Cbup+Ni?~e)|_$tw#bx7T80mE!FRE3Sk4KgC!HoyZ8Ph%2am2{cL
+zZOPlp)zpo{1&faY+f6YFNdq%mHD6h_YAnjpI5U9JrCPN~JMZ~S&ClXP{4-Wxn&D`&
+zRlKH|SR=hfESDLALomZDev6N}`!HsIxhAyqf1$6a!h}@*tIGPl!z*jZS>6$d;}joo
+z+jct@ORM~5E=#4+uIWx>ZWmd9Yr!7N@u6{qjL;jbJM~9W+Iv)W3c|eu9K1UT<of(%
+z2YE16(8L>oaY%4*tmQK&Y@%1ZPACIwD}87<k9dN+@L#Ie#7;P%0gko_1>6K00AzBh
+zrPtv0gM`+s+`qAW=)$5b{flln-iBu2B&8=i5=n3F9ZAAk{nZa2EeEL`saFZO&gRmc
+zVA*dw@Zw0TeK>L?UW1}m?weBQ8Cz6{0!rZa2emx&ADBhSX~87$pxF>!L$lKbgWdsX
+zb^UxO#hW2_1Q*@ZGo(7_B}rI|?y?yf>VK>1Kvv6*h1UYxmg##8H(ht<;hI~7hF`au
+zUGY0ADP8%r?`Qywl=`jz;mz8Y)}>1=%*1lvRd6~|qvgazFfa_V+`j(&h+E=$9kUzU
+zG3t_fQPmmqv9jDFt`mNQNI$OV7xF?WUR8EPXDV#qLpWp&-JR7g&pemtn6TXoT?pGV
+zS0u92xk}$!95f!Xd9@9AwX)?tOFxJ<i}4_2SIq+CYN3#>f<SPgP1;BFe1#+^5V3I<
+zyXA49kWxp5x1VeUmz;93VUR+SY<tY$&t}e@hmrClmlWsth0p?geuJ<(v6icn2lcBt
+zAr^L$$}W#t4V3~u8XR0ckecGe49fjNb<&pQxjD2=#chYIm6~Mkre>^np(3900ejE&
+z;7qKH=y;LSN^<vDi+YUJu>#bUR6BCep6@!>Q^wWJ-W1e$5mf^m+b|TraNyPG4u0#-
+z>2Xk`PlCoTu!zr9on^)|kFjh_Q;s6LbZ#wKY09pEnBk_strvkOG+;Fm9#u;5UGgnH
+zqTRG@I*5{X)R_us=IV_0(0IZs=qJDQ)w^8eg$sx}*LZ(oZ0!m{XsEU6%{f(a-R`Ho
+zmBzntadX|$mJbj1w>~i=RA5ts^5y?JFCM8yHt5JlTFlpk3holqBfI|$(foUZzTWRI
+z2g}F4(bsw2#aR3-NZj4Xqi<Pd`a+lDG-1jFA3&}08*4<$G%UhDuxq-lRtIb3bVz}T
+z7Wmi%m{#EH2qQb0kCnFdkt9@AxG;0xj*BR3gPqg7umxqnr(_QAL;NnF<(_un%G4VB
+zY*#==zHzu?llW9>?|*)AWzc{}Z+1ynxm{G+6a*Icd;BX+*D$MFJGpvia97e3UB9Pn
+z5<fKS9+ADR*D$M^{`xxJpBUe!nvM9}O%`0e-{_5(zqJ{ICeyrMwOF@KpO25j-tTUE
+zNpZUsR6PU{oRyJ3I<xY>UnlN<+b5sO`<V8;E!yUO{k-ZOJ?_#!mg@PugW&(%zeeuX
+z{xqrom?QR_`DWX^j_!W{#LAOB<?@px)=42X3Nv2DAbitXXd5Fw89cUQF=X%dczj>K
+zSMrnhJz2P?KKAP#U9sAisy0Ga9hH#JhiOBR+Aas@Kn+rfTDs;6P5K>R?YCGI`*(KZ
+zN01eutJN(h?Ns|z*;DqsNa7Ztjnr64T$H6k4r{H$lk&7X^WY<et-7vAAL(!_oIMm2
+z+lK09=fOKK{teMj<N`&Z8qeVoh}u`_xp4|VjMTIx=E^-V`{kGj(a`B9h1Kv$Mc3x~
+ziMl_$9%iTieLT20jzs>remIz|o$2*_Cvwt-YBP?rXxh7M-92cYtbDA*r)x~Sx?Y~_
+z^^&i(98mt%aa+g@@RXT1tRm~bb{&eQz54|?2@gzr$>eR>96Mp!bne-2b$S=lqkx;j
+z;ho)Co1?bAebn{RRt@eZ47{Z!*I=UG$XM**WTb%rH@s1ljKFFF(Vlb7VtcHYwvQhv
+z9!niIa(>w>zh}F@f7;yzeQE?}g_&y}6;q&a8@x4nlDjI@;J$l#;`avNEf}0L*6R^J
+z9|Uv~(TOK}TkjC1OQD>vuWP$}^n=m<G9$3aD);2U;hG*_ykh*_5f96rHOv*X<ECpl
+zy?D-Kr`O{tJ-#|ksBW8jo%JPb|19RmN&E}tqI1LH!Q3yB)S2kNMey4N7qPVW<1C1N
+zZCjmeWxsU1<+8jYMWH<#ME9#rpVwl$=0MJmk;YA4aLiDWMyZWVU(Iw_z_%KR-2Gg9
+zil<o*fv{Vy{0!6!t(HAktvgDxQ>zP5#O(%<%A-3tt=pa$gaQPjGmI<Yf}TKkMhA7F
+zeGr%l3k63{%WQO8PG3P`t(T>J6S=t8v~=VUN)7&(Etz92sk1qT>v1(Y#<lOJCn`W=
+zElqV6OaNJ$Ee=|(XfcXpe;n5O{!d9T4==&MMk*f1`dzTR{2g!V+XF%`U}LV2SO;-X
+zwq>~pwI&*D$Dm{gK_PS-pvB9-ZJ~>_H=i$*S?Ng#eO>9M>5w7er+!az!0S=zTeP06
+zM^i9e!bKX0+;w>r<pB^ZpD-Lnt-Mzx4zngBb5}HCK^E|K57HY}_ndZUjV2*kn=%X{
+z3i5X0x;Xu=$!yvetcUu}R?KgG9xP}q!og55O`<2u$G5hI)14Qmd8o#^yFQ{E*P?eL
+zOIPv{#l^A|ug_toDv05UftBHW^n6>byGK|<jhH*81uy%-ae-6jI5}5Sm*KPbfhF?o
+z((i4c{)1vBiMGuoU?@}5^j&74dl_;I>Cm?L?x;W=Jdk)hz{2HbaD$N3JDJH0Ox=tI
+z9++EtJ5(Q8c&jR>vv3(3YE0aSs7`9Mru9AmwF=}O3>;+};2wW#D!P&YG`{Mc&v(um
+zKC>owxHaMFn0R}!TKXM%GxtQ}KaNf|m9l@9s`12FR8c3|){0|Y=r4-%IZHd>XFkKe
+z`38cY&SE{3k&lAca~jW$2fJH&EqFU#q+6=}?hlayEr=nf*ROhEHpMcA|KXxMUw-@0
+zNR;k!OGJR*Y55s~m3ixtycAiG!%O86<a6ux#d76sH>W?|B7HM;M)Pe-nNnNZz9l3e
+zjCxUY^K>?HOj}RTcm?E&J7QzRKf!JORZorr;n{$0PNsAXe^b{E#l`3U`FMk|S2|Yn
+zhF1g-{QmRsJ$PP3YGu6lxo>N2pJC_z=%$+<p@QCj7z(Muxb;HV!AJBoZ=6k1<GLo@
+zoXITz{hE)H-IOKV4gLGxp?JsfSldn~>K)LN#&lV~-j52+cYO)d!NjYt;!C%2iSy~i
+zOCQ`>l{CIf6b9(;2v%KWh)nX!>X_$<%6nZ*Jj1$Zm|g{8jOgr(?>Jb+Spphh^*OkE
+zr!_p3$PJUo*L&d0!T)`~+nQ-FZOqNJBtYTb4N#G5yp6XeM#1U>9uBsNvJ=}U3%2JY
+z(Q!I8fprm$4}{E8V6lr`U6tEItI_<UgYw_Dx`z?{36vk-5tvN>&M6b1veB(~#FE2k
+zNqe=OX|fyEK1|IAXx&?fUXAlrqU$})&0Ehd_F}*v^Suk<(fq%i!nY7Qvl1)w|K?b;
+zc|Xq2XJgNZJ0IY^<hPo{px#_KunZy@0TNGc!kUP?I3H$Xx%|&@Z{U)pd_RuzpzA$;
+z3D!AD8B=l_(~ZGR*AwMSg?zr|WK_L!Z-g87v1*WNc)I&zhg{2K_8S81OFJf7-XQCv
+z=|#YOLRTET82jmI6P(Y5@=nCn6&zx*u5)UjeFuucVz=UkB}>MYfn)!EU^7e~;(U*2
+zZlvkZIGjXaVd*;8eWP7myL&|9)XaXKkDETv{wP9vKbMw1nP+^x;m-wesh!kY&cm`s
+zkO5ghqM>FLM*uJDu5lacg{3Te7lwZP#RyXGa)s~OcmxA@?(NUC-i5%7@rK|g*u8vj
+zjQjlk&thfQziQ<>bY)c!Dgb~m9{>RSf3RRq29B2hTdZvQ|BIE?+?M|?^p@RkYBH$0
+z0_||QTOY?tn@-6_*cR&mGEsX2j0FiCjVMaR<G7S%g4fpF#|-phqt3|ZP!S^2nJmuR
+zRI$SO;ZAGw<r>P5i85y;*N&>{i<Y-yR*UrB#TTFQmE;!HZ%{R!v<{Z%jk1A0q;1w}
+zm2TGHi;PHaC2Th%Wox*Kz-+c&jDAA&%$D{3lkY#to!*Nsb?aC6pTS<CO0Oo{?X}zX
+zwpCV>`$Glh+j3fb9q3`T?pIrxr(Y_%H_n#PyU#kFtG6=SvMQ?=8>)Us1+~zNnqA~L
+zDmDlXKPSGw9^UzTv3^O}=A9+wRltwp?Rv_x-lkTr22_@Otu0Y#)YggJ=;-pb%UXS<
+zRkFny%bsnW7SnhN`Jt{-zy;pU%9T>9uNQ)9-G9~tRVI&TWvx>tC4KH6FNY&<^?Y70
+zV{ezV^^K4Lrl*8PjK7=)%!tVEeox2N8%<Xxcru)K&rv0SAK=w>T~y8UbA-r}Gaafs
+z8_FL+E|i{co+DRRCtM)#NXD?7ef_fgE~9U#0UE&&s%uK@F2K|mS?j9&jh(pv^<rC9
+zv=*`aOD}<rguElej>a)NDl!6rOJ7Q|H4JI<ifYcVZ^Dc^to=sd?Fy`2>>i{0fN)%u
+zj2}9+jXQ2LhK+XDkZeOZ7LYqZ<!q?DO>u$vdwpKNo4GKITRQ+q&>~gzR0a4t)sM84
+zuFt{RJ(i=ZH(qJ|STcJimYu~A{4nZDt0ryg$EwQNlsuw*D@!;+Y@2t|IUl%q7F5dj
+z3m&bIazK;7BQv{sV$`6iPv}`8Y(Y4>FTZp)#cl6Zf%gbl2FatZL(YJfytP)w?TI2~
+zru1OyY+<)V{2yoIsW_2<hp>Jl*}j$n>EdxIGoDcs!p5pqO{)7L%3D9JHTbC*{<-R}
+z#`#Yb=iCCVfc0lm#8ywGG-v>(^?rdjGNqk0(_xSnH1s2P_B@z3{f@J?2ke15^2^X<
+z-1jIXl|V{=64QfjU8Dkh*0AEl>(ug$HcM&&o>|jj-LM3az$##|L=-6N9LiEJODY7i
+z{r7>j!~e~TQg{<LfZQG_eRMQIyDn|vV&wy#JVA*bOI6V*bYlxZ1VAF3jh9qafMSeR
+zXa_!pzOq5*f;YJcMS;oj!PUe|(dm+VC(x0DW+#eJ=k0~Y&S&QY;#54N;pAy1i^75#
+z+vTHChW`%CWNs#I5*45IGS_w@VRQ>ff{Syi%@!E?B98>Jk{AOXu031RlHmi+tWo4L
+ztX`&WW$@|EW!Q=y#O|Erfy8x(qbSHlkbo-48+{QqXtY=k8;KGGV1#4$@jn&EM+Gkj
+z;!=IyT^$-F1v1<^rRHxO-DWn-9Be0!Bkazvo^39Ln#ym);`e;LMS}}$X2sl}yaAnE
+z%GvIP0a(b`9^jt1nii=9%0#rZb0e+iOuh4ZOV?8GjRT2k`>wAR)u6$oS75`0u^~W@
+zrCWfCSprL=U6$DOU~S#yEO!e%ri#xssA1bl8X`;!Lr2b~HdYdkJAk=Fv>cVe<%Q@o
+z05W%igGfC+3-m3G`2_+IoV?;etmC4(sjg|YKnc}*IS7HUt;9gQUDF`WRll7QdO7&&
+zaIc|RPs{1<;fq=62cl!=lf_d%Qpf_7`Wou>`8YICd;i5-HGvl-UHd^&H}0|BEcqt6
+znfjEdYT2-n2fZ)%0H*Qhm`rF$P$L*X8ng%mgsFq2K^ck@t?@B)t%vC{=Yl!(TNQ^`
+zjjmZQ=@O3(2MW1Cd>$!K5`<am<_vfg{8I@dMlFi$f5|{EK(E9e>QzS5#%s1I;*H2_
+z@FChNdjDwQ1PMrp<KZrupD$bU8>`#8_j1Dk`cDM^14bDQfF)q1&MqbN!-FQ+rDJZN
+z7}7-|+{Qj@Q%Z-QRusi(9oZ5p6Xrft1T53w5*g2Nrp9g@`Nz|fUBwju?+T@aB71%~
+z@ej~Ua8Wg#f1hTx|4tn2n$NJoTfsWb61Rk@gcweoGPhZ!ULs(~Oo0L35gm&pO}_E!
+zm1GVq<tHZi)3CNTrxB&_SJLZVbnYYmIN+{d=%XhhHbEl<K)5&nW5m`7JqR38jxclJ
+zSvAn(-mXqX>JZ{sgA2xv3MrQ+&`klw-KH!wW_H>FQLjXqSGn#nLaOkQPx}h%*c53R
+zAcR;<5vC^UF;=pEyGZjG*<|sCzo89O%&DQwj?h75d3as`_Ojem%_z9@tf)f5t_Yi|
+zpb73mE9nt_Ggjty4|mM)J-S^#QWY|JDvCOAPnzrDDFlLko;^HBXBV;+tCXE%8Tq>g
+z7rzR>pLPG11kNCgRUZrmljF;rdzKlrbo-*oT$XkFfMK07gV;*U{f%BeH!#wz({>_h
+z#iaOi@pYCPs9PT`VictqE~%hJJR-Qa$br8%*<RsFoh-7Ecv8^Mbf8cc=^p_@1Wcfq
+ziSnd4Zr?~vHo{$DZU@)=JX!#pK1p7n?w~|H9tty59vz0l(<X9q+HN>|{IXjQ%Lq`u
+zP*!$YEIVkaCl12Sk1&$9d#zHK;nN8ek)0hfH(H6M6B2t3Ww|lB!@&|LVC#fn_qh6T
+zl6zE7Q%P@|TfN*i1C4(g=7JM?Xqsy-FSPzJWlsh4quU7K$A=%|ljNWGVd<qL-k2l4
+zy;GVG0izvL>*Fw;4Y=+tRsOtH<uvU;d>Aqvn>QnrI9eH02@mA$I~TL<FYi(VVFsJr
+z8S}L*(e!C&8g51w(-A{qP6*)~s<1nvvod-sWQB?t0j@gUWZhlI^D`C$!^J%z{wXv8
+zqX||^G%^1^Xoju*u%SV*_AH#N2e$F&f-Uf0iVy+ah!MTQQro-CHS?AYrI>(t9Ab~S
+z4vqX)k|KXci1!yG(!47);vQW{IpHRhPn;?akv2?GR$)PiNu=Etzo8}jOh<T5EE3J#
+z%#2|21HPJ2Zj$y>TfWcHia&W|!+JUm&n7rKv9_eG;tp^PH;r@Mt!L9yrz>gX@G*9`
+za_}6IgSj6Y6-Q~v$$}-I0}u}`#ZVA1{-b7}bM*kG1r3!62Zt;HAqKZK?s-Umo7nsS
+zcIysW)jc&JTu@Yt%g5aq#hT<DnR*I$uj$ii5+=si9qt7LvhXn^3grVABZlT^6lE{*
+z2CSUdEGmAQvi+9M5b&MSS4QvPo1vKeU%o((otDb}MGS)@=W8AZIE(z8sr)zP7Ky&J
+z`~!RN$Kb7>ouUsRAbAt!B)&!c_o;>!6vVKQ=c|i3@D&Bwyy7jnZ_U~ytYpQTuNiX~
+zM#6GsoSmm;!$7J9t0-?$9LY`5Ca5(%Ev$SQmiZ(V{(=r{EOOcpor28PVudB95o+VY
+zAehw8^sXCt`Cw@WK6H+)TUJ-nkyHaXjHG$^^EMpoR2#IuD|1M~g5K+v0xtAkp6k}u
+zL86Mkp&`l8@xu^Zz=Ty=jy+Ct0V!ZebZc7t(YB`yG}M%WYd~uBOCE_q<%v4zq!22&
+z3S{B(jGF6p?~4A*?%Th4cExVpZg_Zf51v{{Jr{q#L7a`eJ>wERECn}E=waj#`rHC2
+z9W#Ry6$&*%JivHHAKyHQM<@w%c8~ja;HR6uz6$0K&VCB#>97IdKp+A3WF~ek>vztq
+ziI`ZOOzOx0oAcMqxqKkL3r2B+US{A>vK(IHq%=A4l_vk8veL2$OBc9lTcW@5o3oz&
+zHg5^;e8431$)2Go!l!JkF}kvjHWNS+Zp*O;v>+b|?sLZl^o_e%lwwOil^<*GYnkyb
+z?4Z>?GX$6<ITwI5p8mhY<&uCX#VPMLk#No=?;{~^j!=?IpG+0oQie)NOJ2Vu45X6~
+zSgP{oAWtKizw-dMENQ~>X^j`dbGjLDGtQ>_ULTy6`u<<rDBAKO(diZg?{I?@B=9Eq
+zw5Cxakh?z|QZ(`sFDx9c`k&>rOeziIOLi<Tg^hyM<KM|fx@{rcxs&#%*@TejL6{}i
+z4W>#z5s(U;@65W4M@age^mBHF*1YBGMcx~^PLav<RcAo(IzN|Otz1XnwHKi$IoK_V
+zWp9v24#q`R!VA&X3=GK&3<Eb+#|VJhL_-`4y7jucs#gKU`;pHhs~Ey~1m77AT`XsM
+z^AcN`hxz8vwcJRpdzMOJZisbuFKq&<*F}Af<tfrf{SNNgO%n+4G&6A`)qU)VrWSiU
+zlV9z(ZnTci)X(aLR4qPcQkvgj4uxVeH+aGe3z{!D$}mEGZs`MSNQP6V{5^`OVVbC;
+zftEV4<&}fuls<qP3sb<ar(2W;r62`vjOQYI+dD{%^%-*UYcX3TO~MKe-HX{w&v!rw
+zjo>zZLr9h9d$@7sTZS|$Hf$x%fNj&u(tgHxtVYFako#hMJyZbM+lak%sX(zXxhGO-
+z{;RtWTJaqzFxpCLB(n4ClgD9z(J9iU4YTzO)AhnEI7Is$0yEmspC(=0irv|NqqJyR
+z=aa3S<a3xX@Ji`2UlMZN0Pvu_5Tk$>zEr@`{FcCABqoLYAsh~otg2^&m&3l&lrunM
+zS>#09`7&u^_hPiZ71*p}(mP@b(fvv45h!qQ8=3xTC?Ft?p~}TgEK))oonE|IlzjzB
+zD#knmhAgl*XE7rjbxCL1TA~^CY&a2aq|w6>(_9K0!=n+QcmWulUG&B0WKkhw-yCm$
+zsSc;l8nTJWlg(<h8rxq0bZB0iJsA$ccl~sGulq;AqFjxZLns{CpnnkIolR&8>tl``
+z*fw!Z0StP$7dnm|A~bh1#%L&45D16{N!AON3Dl66lsS%XWdmhVOCl$LjDp6NWjS`G
+z=>hs}(5KHikRoUZL2+5c)T*u!>Gw_mx@g0s{ubAch4}ElSc{06vtqL`ejSU6wHGW;
+z=cfb>jU+|Us%F#@_0$CPJwr6~n?phlVhrxb3K$FhdCovUp7BB*@dtofpg@2a)PDdB
+zjPM}Q=V1vd2N<4e5&i?5_=;^AvAD5tL+x#pETqb41S)5@>l$BfCP^@6f}rOE5?zl1
+zf;f?YY49dO>lR4s(O|CJw1TYwg1$tMpIuwBeWqPzK5C5auW$sJU2cjmEn)z060=zw
+z%77J@klFb|K@<-VYt6;w=PnGs&c?R<c5ro@g-PFcSj`CRPQhvbKQp`jfQ}ej=1plo
+zOg+&m0CutNUMt@~5^eG|gBjZ<c`k3&bpi=)Bo}3YWzdJfEWZW|c7wWFZE>|-FWzrx
+zrYqB@KoHDVM8~5r{S910oVr6E>dyKgVZkPxd37}c=!vl2W38&cM^JCHod=sIlU&Z3
+zG-P%!%{3it+#~%Dl(L*KWbtec94+?zmC+PPZU!WU;c8ItDTP}eJ{WFMA-N8nqPQQ~
+z55sjI`0*}z#WgU8I8Tm*taB~~lZ**E4+IffBfGeH!C@8_VwX1Co1fjf#aTC=9dve6
+zpxv@=xB<F0>4ZPSC6T>`4QGL3yd)vuvEV|vdY_Jxu7qg8Q|!Q63t32h&*Tn8tpiCw
+zX*w!hIAICHo}pNP!yftirbyMF+cpuc+;Es&Q^sPPjGQG8mW8yn*jk55OLd#1QR>FI
+zoK|u%DBf<$O@i5A0Ttd=I*1_dg;E_R9v9*^|M<kz^R>E~cFTq6T)=~(p3>4jAr=VB
+z^LcpcBcewJ2<G}$K(s8m8<zYFkU}YyAa(%_wzm$Fpq`0#YIX?qbHS@Gs+o!Qpn6+6
+z4xF)H^dYKQ7Du4|Bd;e0oITHRQKvr!y%qY@jQ!w5n)r0pUv|3Z*^x%Zf1e8&QJr4M
+zi*>L~IAS_;NT~2jNyBko4**JEP%GlaKOY6`aY~-yYhNrq6&|0f=3^A|SDAlQuAetr
+z%)ON~=bGJao9APHWdn?toy2#^CJ!Lg0e@;oRb2grH+#NG8t}elD;eRlz*qAJzj?tP
+z2M2t<9yc9F7X@I_dDDG}suATX#D8```6Nb(0L~)~hfWYA^XeK7L(1k`@;&5`yNaYb
+zDpE>xN^R81iaTBW<w^oP@m%CL8j!XLH|8^72NtiM2@X%Hk=5fPJmJ|9j*3-fh2SvM
+zF%>CM^buoKKyRAyq}ihtwpSyL3-UCV@B_;x2!TnP(mCZ8hcdo|PMjcu%ff;h_=v-O
+zZukU*L}ay8GuD7u{<v9;3d<eIy8Yc}@2)#1BoWZ*V$43MCs}hk9;7u)irf?;C`U3|
+z48%5|QQASc3q1Tv**<yu(EU1|hc~$5>l7%~Hzz3a_a2i-fYPf9V*aq$!63B`51Q3<
+zKPQnh$5<#}HvHxsnx6b_#aAv>f;Q`CP(As;zn>xHjpCHa9yi(vCZ!!Ry1mn$M}@rb
+z)Tb_nD}g+|7+c|WH6fUh<W&g=-on&nn!KcI1$B-t^XsyH&(qt7gyUbz269lw2n@5a
+z|C0Q%FOu1?*}-q3i%8c|fkCp=pSO=$X^7?Sx+JWWDeG?Eckgp4yzb*p?G1}83K>W7
+zOT?fXLB>#59FLx8+t-<3{hQ_Z6s+>5lNa4!BnRNIb}m6NJA5hTiwK>^3=L_S0Z7ao
+zxxj~12x|i24``IyADfj`%DX35=%%?`WTeJ7keYQr^4@~J#7O*_pDH-Ddp9acMl<L2
+zd|Mdef*uO0dX)`%0iC|Bzan+#3VfbPo4juOR&&G@?BQUtkbNM=i@aoXV~@U!06cCI
+zgL65}RqDPazLT81bf@?O2<6f-F+^IFr1leU<5qul^1lXUm#FXw)*nL{FmjD{<}T|$
+z&voVw9(CN<>!?!>ZO04j!Z2=Oa;zpg-Mz$SW^sa$MN8@!03*?}z!OvP&I?nQ_+ScD
+zVYIRgrn=<h7gUgTiY`eo({nn{wlE!Dy@<96M2HPPzut4^pbb~8+nH3eW9`g${#1w*
+zA>_$1Z{7-a$l4=4h4e^n-5-BgRfOi_&9+=!DwmkJ!F;O*J0V)OJ^^!d-?+ggjm$Yy
+z+&a|^_L&=PKJ5W<MeK0Q6vp9OHrGsFXwvmqkuvl15DHymx2r7t^lA2|nM9!eDAcB@
+z6gN#2Y0C9-J@HF`2+{eYeCu+NxY3_LQ)Oie>79EjO}T;jMJC!12rjaEEJ5u`U5dV^
+zl2}iMgWNV7fOMSPz?H(X<RMv1t<K*)wn~x&JP1*kT;AAEH2!=+cPd?q(#u~XSxs&B
+z%!pmfO>0)UwCK-?4cfTpoO&e`018uf^({s=_5nm_{@(BP9dB>r13GwM(wI)$LcCm)
+zv|}<t+OoAp@du*;K(1QGVIpau>`qv5eT1KufM^0m<Z>jSM9(-U6$Xq0KzcO3EtaWo
+z-cnE!+{`zr2lZpSS-?(rJ22uMwOp^RVN++Yy8dJ>9i9SdOKWJbChqD>q;QxFYtbbt
+zl4C>SsIGqIVI|OW!Czi9rf|z-T*Jq<magktDUL@-HH4eQTqmsYs1<zuA8KR&KN(nI
+zDbgV7AGI+M1pol<KQgehleyLZ5F6Y7e-*e`-P)G874fG=uYW_L>Axs@2OwRdC0(>_
+zo4ak>ws)JmZQHhO+qP}&wr$&fedf-^;JlgnBkGT;sECZr6<KQ~zvLWU*P4~1jT}yS
+z(Vp388-2w>2?<DE!<DF{uotqYffo4o$RT)EegYLar%QGo+nr>Z=4M(LUhN{Uf_%Ve
+z)PQcPse;~2=wh~)vR>j<ZhkMkbgr0eemD1erntZvGk+Mz`?PG{RkU}fE`uA#+Q$7E
+zcKtdu);=uIuqxMLqf%qz<L%=`Rz0}NmKNus@$!*jX;UFu5|c5Uh_`ny<%fR2)2a2+
+zuv(cPg-^7cu2L1Tx@S*%v+O;!JXdiCZoz7vF@%0g!V}wlmi*y*e>R_C^lfjjsU2L=
+zSNCAfkdYN(QFSR`>x!-?hR!Q4tUTKumMr|VBjT$plT?Z|64RKJREv#R*z=qZE6Gg`
+zY2huVG4Kt3i~omL5gQ<5zrud$Koe^9V>85O<LlBGl_#?$E^8S3vz!t~tE5-|;s9hI
+zc?Vv_vgwZ$4TiSPM7;fNw(~2Dl%Gck$lni&tEr=L?iG)8I%8iUZYU#R))>FPDOGr5
+z`O|1Qcs;#fPvZz$V47055H<St+;iRGA;Xhz&eyA}>L1?>_7F3n`JvYlf|W7-vG6e3
+zpv@P9ExMSP1YNd`R*jDR;KNJD$ePiD`_kNT<&J9#8}ql46QAwzd$$UPF>Yqqz`FOZ
+z{#}M_(?gq=F)b;3U7cj|4uWyj?dlfF;paU)0ew86Km8KVWXk=wNy^SEaRsc^cfSFl
+z3=vItnBoWIZo4*=$D5?~8+M{ocn+h(9Z`F%65}*tv>!e6s)3!zKFtf5F*5)m<KK~$
+zd}fHFc!9e4t!rQ)abRF@f6HP6FhxR8V98~O?z?SaY3=M&Ym7PQS>{gkrF3N7@l=3}
+zYQE26=GLHSKKd%vRY6zzmi+mSkUWWC)|c_rSk&YqKR_E)Z3Yx7KrExHR0K(^Xja-O
+zC=-=_l4&y_MagD{K^Iy@n+<#%Vzj0ZWJharg?+lJis69eI)}6ofl|iD(2W{FQaJVy
+ziuSfjEI6Z+?%2nndW#JU5X!`jY`fd}h!Zfd5V^$y72xqt(M{NhXna!8;_EBYOesAF
+zFO1mx%QC1U76OZD-u8DVml4=6YGT(>k7F79u&gBoR5pvT$eL2uea9WkPGI6Ld-XTU
+zDGWEzu}iatw3MabTJu(-Dj27!<hbmIR=rKwWf^Md1r}7^l1wi4F}0@(>$jn&;&Pp-
+z%`6fzaF?sSBurJ25A@r&te0$OYsb3@^!G@*WRpAz`W6z0eJ(U1>W8oLNtz{DbeRm{
+zv?yiPOM-m&6v5|U{6y)UH_moH<Rx5gGB0hd*f|T@O+S!Gwxv(k)7CR_uvO`-0*<)S
+z(qurak?D#H`-S*ulhumPi!TBleo?Fd|CZ(&vd7GRKbWFtKk}tP!^#*q)){dHX19Q2
+zcrxn8^U|;1D47z-K}fu=2-E@3Cki8`xXRPn=uYfMC#27yc=B9#{6eF76q*3E=5jJd
+zTpVh)iNT>A*vvpp><Vd1=fsBa3KasH8q<i)u6f-2?tnA#sEN`*mCKnp{YwCa^vNyX
+z9f37NYZ}HxE)0y^M3CYT!2UDXxD6b?EvZR%mhh^u2X&=m7*?l;I(~AmxbodQGiyOR
+zWbWlqW?kVlM9#-3qY9Jr?XQGLX`4HFgc%5*W~kW<ePzhtW54Or#5{|ktgIDS-_mbc
+z+m=I;hy^QZT$<zHKdTknFx!CVENNsz6s1t&^Q!HC<LEvC5*I>w=Yt1w#GuZtvnz}E
+zyKY~Uu?%NWp0e9ymW+}J2jX&4j51*<o2OzJZ?-^WBzU5W9aiR~-aqUJ_s$6c_{orW
+zbVVWok?$7l<-x(iW**Fi1&vRvQE0&cyG)F$k`ISVF-5Ul?5*ilx`pM=3)swaw7rC8
+z_W7*SxZR`mxrdIv=5)W`CpuU5RU|P|R;pmW-5677cee70w>%a&I#m=<?w9t4>z~lO
+zy46|4oqk+nNt}BH0t=~|&u?C+W&)|*#D*lQ0R6&uAk9s7>%BTJIoS}hV(_L(OuB1I
+z9vO@};2#3QjV}8cKhkBjzD{8j#XXO5eB3W0Gyw653S{ecGDUcecu{x$oD%_$cAJcY
+zoXK?&E#KF%aDxJ>FvmL%x&FYNrb{gvV8S>v8b6X66i2Z&IyaeE;uH6kp8EYP%;GLF
+z9tb1!`@(E1+mXbT7*oC5VWV3#g?pH4c8Y~mQ@W}ULyyEOwL1_ox313h6L699+-x|Q
+zPz$(*lxfr_>S>|C8E5zI<XI*vJ0hk}a4wbBs>5~x2`CD;%HV830;Lu7Sx4-cbYeMH
+zpxXu0Bh)KF)A-CGQ+NYs?#7pC%?+quJVuFCZ*thM!r6HO7AnhPxwk1m>Go*Qo2@QI
+zm332Bt*1Eqf@k~lueEe1@@(MhUd#2@&ezmdQ&At7k4Kn^2{QJrWNM#6cJ<UMNZ2%8
+z5B13DH}^;oCg9&5-!D%g%_=l#VZDnQdvW*cL9??F7O*6-4(yL6qgD2i@~v`)EwtBD
+zc({`)xIUFSmZ#=yR-3O5?=Ak$PbY)TnXw4FaR$7*OnuTPFdg`My5uKSf9+m_WCkx=
+zPYm5R9%QMKy1S`>^I$PeadK*&8mSErGGR~)*KOu2v>vrkvT51bQPDOL<WvkDV$U9M
+zp}KmjJ2Oum<qZfQvNpbFJ=kS@>BJM@aBxM%rhuShEZjQh#CbX1C=X9Wa-o_8JMEZ#
+ztF=IpZK$4rR+T5+>=0OcPY)M|F&TE5QLd&<y=E(zX+OKsByN;O31OB~sA>x4sz)_?
+zL{}JmO<|UpP1ZKX@?M5AhuiX9ilfvB9cM97T|=uWNt>U+pI0>{ZhN;}MegiOGVXGB
+z(^Mtt9U2_vf7aK;AkVzX)RC>`DmNBdH9N?tUqAhGKEeNy+J*x7$4@*+LgHU;{`K$w
+zv1y!*tPO4K>6Dcq0f5C--&D2LWYnBop#cCvo&W&=Kz<A$fPYR0^Z(jL|4K(k$IRNy
+zQAg*0n@Rs5xuW_%Ac_7TNE%x@nHicny8V|t{)1PDe~~p?GS$xP=V|cgBK+qB|C`K8
+zQH@V4&rHZj{~eu_QXZY88Kt9?pMjv2pq6+Hf;b~b32^0vA`uC58%iTddv)W~f_m{Y
+z(N@=&H4b<1*48j~H<Kq+LzZR^bamK}LXJ@QxRdhtBSk4ODlR@DZ#_geDj`hE8yf=s
+zmxcbnLJZs~z?uFC5b|?T{Bt4n^$aXr^z02C3~a1y^&J0;g`fCe+>=*RAMK4x1JAk}
+zm5B`fFnycmX7&o@+zf>^%$csD1;rm5pI#iBnVOK0nwU@?my-yJ3g(x;5I>jC9TYjZ
+zHy9sJ7+V<a@9xPh?&R6oPO54yYey-}G6?{x$(|4a{qICELRNtH0|Efx0098__0L6-
+zmJkw=RTTL@y7&*}`FFbbM`6l(ogSw9oFX(^qxWP9*G18?2+>$GxRQBdwk}C6zyLJ4
+z{s$0AY&9avaSja^0e?p9?w!EO9Xg0Pwqq%}k1_de{`>&WEAagZu-nVa9q5uvn0q1S
+zH?zU_a|~>xvNLBpLMC1x=*gv_OO0)6I>Q>R-=`tX3s0jL6RsuDtPoRo|ClTw_y;Yk
+zwc|b@mZ^M<p2@{e`JPuNbHdOOTsng1Io3hI-<IP7TmxhYn`<!a9p3!MjyG1jXZi@o
+zX3SJ<KXV(B*4CAGhrpj6-L_<QBwGWnOs=~dm0ugPn<%E9+Jd^`?r^ETo}{)9S?lvX
+zSq52uJNkln{r)08Ug03Zfc#yTCLclP6Q~{NjqS_3<~R@OXbY*#G0nSe&gUO3dLEgU
+z(9(UsXU;FoGU1G&%0Ts5WknLa^x%W}5}eiGbB;y|x{k!8X5so?|Epfj_RCASLKWK4
+zcYZ|Krf7z30D#Lw6XUEs4)Foagzy#N_NE$h=b%LW`a3nIv<K;TMbWA)4gb<_vqwUv
+zxIWyPTd$N}(exw@>|VGjsRqy{5N+$-5b6ey!D-2ecCex}>T)fpLBq78bLr&i+TJ#W
+zpO?1|q6!7RAT~-03&JW8S=BdV=!_}G8MQ^sBzCk(U~s6*qQ&Q#7Ntw=3Qhd4FlJKf
+zv@GSJf{90n%80<X{_v%$+|v@_w=C_;2k;8YR!yGTm}+I*$Plz3ocj@4T1E6q=??0|
+zvA6zw5pI$M5ylJ7PT(0y_@NK%UkXIZ5)2B;SW(j;istvc?Qu=wz8329g2@`FkOfYA
+zf$0*q)hMcH8Z4ge5Y00le<EB2M?t3Jpr;kRIq|gb@&0eU&_6cS^4c0GtRF}5iU0tB
+z;GZkDm7amAnYGb>@ohG#OU10SB6Pp2B4~mn1BppQWcypsg0JO=Jj`5J=|MC~ubV&6
+zlXGTR&3<oT#v_HmkI6>7)h>nK?{Rly&N+Ep73nKfn%^tA45%xaJH>iUgyYuma@)1x
+zm3Q+vsIds6+F*5C{6t4VErRxx2SNbRv1h|&?UbK!E1vzCP=JNWivHT@^lY(w)|I-o
+zR8+t_xlp^id2T)_be?sn9-BD%)1kN|f6CuAgT|}oVu*CV-JS0ht{E#+j&<aT!XCy|
+zB*Y5Vdpx!Nse0P$-7qKR1=<$X{B50j#ulmCYWuCZ9R|y#t*i6K;nSgiBOyWi@=D$A
+z^~6p#Ilg*Sq0G@cEHX~s73<Uq+JrAEZsGr;L^Tbh(_ffri$$u`!YJzl{^cHHdF|>{
+z&J|a7%rrg^B=4r~yjMouP>#tuDZX|q(98F(VdFPpQh~=eQoMajdKxKXx-)3#G>{xJ
+zp*~8@Y(Bv6p}k=!N#02QC>kaZV0Q_V#fm8L_2xmy@enWI?Ys-N(aa;@*;CSApCWV>
+zR^C(^7*ah>boyHFEO?yYo-a<a+)5tQK5D%HUMAr64eW|BTEMLyB76=z@wSQg`vW`K
+zrq_#hjC(kCTCTah0yWSTVam@C_3LMpIwa~JAY3Fpe5u70QKMD<AQtsswhLL>m0bAT
+z4rRfidqg&8Jnh9SX!5s#t-R8?MOIYIur`r84t%x@fX2=bjP*tB;*iSb21cx`_G>8}
+z$4JP0Mgy?@P;RG^QNY7}1Q96W7K$R|xk7k_c{?O2Vju2dKahf<(EQ6vV-w1mirIwf
+z?(TZc&Q?c2h;?`F>BJ2argXF>(%}0$bIE^B1B6c4^tB0iDF!!~xd;laqZ*j$5d;d(
+zGDMY%nVf$nCt$|E5aKIktmIbZq>Cas?!wIhfO6FF5_psL=Q{zwLWQ1293o!;nU^B|
+z5v7@4WxJr#sXJf+_*;{e{9&+-d$uof7KX3MhwobV#7N$Z409AFYU`>Z*L4~<uvW2B
+ztqL=jN@aBCHL#>T2B05z^_m98j~CQ~Q;;Wu1--;ty?yFCKyQEc{8v-Y06!|y(L^5}
+z;ias^;}vc1cQ<?p(8Ps*z*^+5hSzi8Kv}a0)q5zdJH=)5?*821zEW;iv(7jIpk8d~
+zJA?7)qWF*@)C<jTDWsW@Hc0HTU}{n7Tta)yxGhH0012eu(mW=RJcIL#VJ>1IxMx>p
+zOGM2Zo0~Gz@FniL!(=eowOye+9-Lut^pcF$M0rNgg$OyqPV)LWiOyixh%NYo3I_eG
+zMO|_99(_Uv%u`#R_)|{R_-c+E{@~apVB?fh`Pg=?>S=K|?55b83!>O3rQF+FDy4>d
+zbBpdqv1Ci^vX#XlC%U&7=+Ctz1*Dw$Uy6nr%H5l;_^9{g-q>sL{)lT87q7it;T9QJ
+ztFx$YQ%W;{M+hlt2k#pR3o=_$vLdflUs85LMStDttsmIfr`JC#pR4=wCM5X_BwrW`
+zIVBZ~?gP^JL`5{yadrz2BRcc3X%!OJ`r0*@d=s}Kne2d<lC;HXtTTll^A@^)S?>)(
+zE?seKkXb>M39vFKVg_ajh1!;W`?UWFjdtv3<9X-9_=5*w+VJi{L-*@p7!9-6iIOee
+zSpI}}0XCZsYwPMMvv6v?6iCzqNwMNM#V{R{i#&o!3>h>8%p805yFM+}T@>w1`y_?~
+z6x|1#7>WHuwD|BOhz%N<+^@R>&n<-MGD5(B!gIS0ojRglVvw(U6%EZ-dZJ_r`Q8$Q
+z=-yO*R^yL79}MGX0t7Js4`#v;2n>D_q*-Dw@vp(a+D!CBI)NrwDSr!I`kvgcZTH7t
+zaG(QuXzv%YJ^h%l15A<EQ=)d04))iX*0a=R-^pHS3SSO%d=PXLTaUKH^7P{jC%FqX
+za-JBQyVSE|w&<X|dO(GL9sPu`Yh_;BG&80#;~XV5sOc@g;?V%5-qv*#csf<PzUNN-
+zK<{_x%n#Y6Pjro%`{Xii((PllTD=3?aH9Qoh-A{5_KSu+dxl~-!vsKzW}?wO9ALeu
+zS4)SY6o3tK^Df@Yg;lGfxO8AFe8I>p{E!LM8c0j6oI#}jnlbRNwT?{n4aUP6(++OQ
+zkJH^wom8bEXB9K|(gz2MJIwdP?xy!M`Z$3d>y(h}-NXNh&38*}H~^wWQuMA#czTvv
+zjyp8lp%^9%KhCZbm(ka$k|QFg9;kKx2%Q}M!3ph$g9r%l^wlEOhQA}k$w8u7^3Q=?
+zVn#aMox^8$?R<m7ePPIG%>12YR1e>|9s^AEi-dt!krm$qBse%m7N1BbRw{k{e#lV*
+zhZ^y;6-i8oS!kyTv1S;%5;H?+h+tTB&oTahV&WlQip&AfktHkns-|&$>Z0rv=s@h3
+zu=fXCGCy2(Z;GPy$mPSV53H|n?)0AvgeSMql4H;=?crD1MEP*SR*rTuus}=Uh!CX%
+zV_s#m<r@KqV?SY#N*|gTEwbp-9eJIpaJ8)DQ`-3T+htPy8t~t)sDIZ<dYe@{6S+~z
+z*;29kA>2y=zEe2+{d9#uiM{VAW)Fi&i3%gcl=}tQe2mr=o5a+vBIWNeeO~xM<(fE?
+z$Vsq^=5?tjF&QXnMvGN1Wt*F9XCWo*TH1kAy^yQ8j@cf|kW7YtWl!&sQFx>sIEUqY
+zknE7r476=&^}OL28fz2BOe04e%tGo0Yj`+I+j)hUFVn#7!hb$>eThIy=xOz2V%IJ5
+zDAq8P`ip&nl=%7l|9D_%|H0_6G;%dFu(AFhcuR_(eUkrh_85Mw^Z#hX{BL^yO=rm|
+zq3<6CdhoS36cE=S1<bWJehA=ZUT>|oGd7q&+(}>*H6{`+8^+UBK_>H0L_S0<Z!G6P
+ztUV$xM1@IgEhch2UN6@hB5rr0<540~+2zr(aDI(S(>SJ_QMLqR`zA{I=8>{n9j7G*
+zb+m59PvIaesT{ez+7BETUg8&g3B|T`^sA@;btoFUFE4n#u3%5fZyz;7HpqGmGw^3`
+z-EiWbK6F8j#U}VfE#zCf&9W2nq}YZ~1sd+4Oj6L$RM8O*sl4K@k868<5cK|;{sy_o
+zHP#zW(qPAYKqN7l5TfHZrVe-LKcThop45dmR<?$ro1o_AbwLu8d<Kz*c4iG;CZul1
+z!U&v@Mh_}y)CZyKR%6$PBg<mDtekz~M+df>PkilBTD=e?@<554P<28uib$0!TT#sm
+zFVoz7oZA%JA8ajkmRK*JCYq=g_Afhb{S)R~**pf!?6oiZy8X7Vk(RxpoCV-kFys1A
+zpY|{GxQAKrTG8`2rCHMak`{U1xAu$lgS9Rz(?Zxq1YCQa;U1WBG1(I0S}u&E%usD^
+z=JiC*da#(yrlW!q=5DPGXtyw$NT=lXxqn1EcJgW2U^+GhP0^-?sR`>G49ve6lQc#T
+z8r&Zh^}YLKU$(pm?=VF4aOjs>Vp`G)nyn;wnQFd({;gGf_jlYD@<*A3U;zM#{;7iM
+zS(@oN=op$A{8v6}mCCyHx+sFzvZ{0uxCGz;JVb5?fKx0tF~u4oT`)n-P+sFq_{0^h
+zli94#^+nJDtpr;zGEnpB;ba<<3GD$;x44r&&`VkJ_+(^zR|eppFQ1tYPR(AiG87-A
+zRKSkJC|}@p+&=MEai_ouKq__vW$~f!Vg2A`+$Atx4qvXK!n@F-c(T!Z3^&$w$YPdG
+z^vqM8*T0oeZ%t26pvkqHci|bA&PDQxn4Nm&HK)@V`$JoxLLR5ZqD^PEjJ0s*o%eX|
+zPtk@cOy?n3tT)p=4YaLXuyHi6R-zmu3`ENwCO7;LqE8SqEd8M$&?t%+z(A!f^)L{q
+zTzVk%Iny2Q+q2B&bNEw$NlXjBB|$x8ivWX^_K3E8WpOCEc{MUdMm(1UJShWM&M%nS
+z?^`!clBWx$4s)L11`FUPU>D+51N98SnoJ=o+uiDM)igoX<C&PUT;MABAgMv@FT!9U
+zfwY83U~kOH(t)7cPkTLGr9l5mGpE*&8Qlw7?R@2N)qApC3Ai+huLn|x_Vg#)9jU3c
+zKS{v3NN@M%vO0@06W$=vLaqt0=KCt#q}v)QqAtxTd|jTiW$;<demE9k9pfVHYGfz6
+z2-aT5^5zuIR?Z9QH~Mx+vLz}gu<p6d&MdI{T1s!Ga^KpY9adJGy{R75M%!!6eM{-&
+zN#)3iLP~5>pevzhD}0Y8Mc@!o>s;aR+Uk{dgoW=*K$}dP4R%7v7IIfvMyndMZI*<$
+zj<FjbA1s8A)^L<SJUHAbu1hw{%1E&f81FOdD|HfmFrK7_Y!Nu-?axm?J@H2a4d`+i
+zY3(((xi+;_JS{*BG&qOoBEo(FZD9*xw=qDt9;VxG<Xy7|`LJl^S&*+M$@w3YK(+Hy
+zBAjPc+Ds5y6R_nj7@A__fr(DUzAzSKwaRjh&p?SVSyK9AJ}uzUCbguus=V(Mhe+B>
+zs$q*_R(6UUo$X{bAE^}l8jMkT)Cc`r)q!l!vvapP{_3Oj+Z1Pnz`q({p9~S&<kkym
+zTC6VRQeu-k$lNv6Ov-~K%%eqq(yp?%<-AP(y{Z5%Ylf9l*w<a+k#Mp~)S#roqev`o
+zpUq&ER1F5z3w)@Hug*r(CHdvC4VB{U1v{@64}vF;0&<<TA!;l7VxiVM4t2NztE3aJ
+z<YP}u=!5nUy?AXdd(supq%)f&wmV@&IG&dClt+T^85uQMpkoI9BC^r-#EWcDQ}6)w
+zlCWp)vUsD;TtaHB%OKn{o0V+8@vBE&^Le#3pB=HwL~6Im$JuJQXk~1i-h2hGOhi1@
+zxY0Gp88C3mt-^a{b3~f+yI_%%e4(K!C!BQ?JPyN*=mVzcbpL5l#)dEQJ^Fjq?0Hf<
+z>CtrV8~ESuGT?TPA2~m<G3lS9i|n88GLDY+X8KN!M*p3i#w$hus-GS~<e6v4Hv@{t
+zZ%1$@Nels!;d4>fa@?@|>4}umDtt!=2IhNu5_{hfq7hNg;|x^OqOj6Hy&5XKd^T3W
+zBs3bbXdGZ!$#LlBF(ELdOhR9Lnx^Q2Ks2TxtVDpUairGZi91Il)|Ii_ihKJ+?=T*(
+zc&2YM#uS_0uLnIDvU0R{fX-FK<<i~ogNUq(PrExW`2F79v4KtJ-^#z7l~Pmk6I3Gv
+z1pvVPr}F<dVroqaT|Y-Q!qzKFh_nPjtJSb`KFM0UAewS~{l5IqiS0`R7N3c4vrV+$
+zD~p(`$f9O)clFLy^mXcF3m=25)xu``e)&%GQQv_}{V~JIHHoo(O~4vSzcLFq-JHQt
+zY<WCvX8Sxc_WCQmJ7C|m?~(p(e`d|q{`$?>H7E-Y&H#wc+8u;%u!T?dVu!26!3r4w
+z$rKD)&Y8W{tR9ye_G{HiNjhdqm@k?C$>V$P4cydgyN#ZCI4>Z}n;z`J(=Vo6^rpjw
+zqw2c!kzLkM-(1|d*YfJqq19b3_c_7B3aQVRa|6PoADTX;(CayckkINmitES6Mi&f3
+z*Xx8PNZE`V{KTWNwO@UGa;^PD3E<tG-B8sCDtQNln=%Anz8G=1uv4{r=?AOYJSY{I
+z2wJrz@ja6qV=*QSctw+@#ZOvP*=1yloghS(1g2x+q>1Wxvm(aTvXe<r-Sxk<hu*7J
+z5VVMevQ<VE*A1+8HT!)6QY#(7Ibfd#0LV`Pi_$76WF<-(_rN^-*8m^A7Kw7|lrG1>
+z&lo$1dM44&LckD*vKpq?sSj1Dsk{)4zgaDqlZL%on3c#`AEXYg2r%nD|7F2w=BsaC
+zD^{GB4kSYQfKiA<0r?`cA~BxGO9ZGLi6U5V1CnuIk(EQw{I;W*wA~O>=a$5+<4cUV
+zF`-dKAC*}Vu07i#pqLdXS;3gVv^Yq^fbv0(yfE5qQG+<vZh(kE;J1>a%wy*ghS0zq
+zMA6xJF15|*Yp}jN^&xZmkfRZ1m4cvx6|m_jesE4NDY|2|U@im2FT$BbHFMfEj9Yo%
+zqkf`9ef1rM#4-qTL;lrVG`2>ng*?D*Q?^X@b5o9sP^CIGZO=N14dNXj*XCjT_XX*W
+zW!E=ZeM2)i8@uV1|0nXc|5KuorY56dfC~T+?*#BO#1IF-%-Y$;!ier)(-013uKzpO
+z_V%9_YE=HQN?2!&|0&e)OR$(w$iKO$Yrs=TTZl3_Q0usNj_;KH%QciQZaN4fLJ7jo
+zG`{%xd~OXu{8FBBt29pNdj%dP>QVq1FW$5LO+7%KNPupcR(L@XbpDe4D$$#!<rZJ0
+zPOXO2uwJFrHw}5e=i7Du{NA-i<(zHVZ06b%Z@B}%I+oF5<L&F?ZHVXk5z+$Z7a^4$
+zPJGn`bf%eIEkq#b3kj-un@HDu!TYE`7J#T(oY`PF_K}pbb&r?;>DXk1y=O8e=&l!;
+z?}oo&3~kF#r3yeCU*vhGARF%{NbG$ANC=I@r+KgDj^v>gj2LN$ubTO2l3u`9Xbp5F
+z;tRf~Utwk_M-a&l3~Ng;KO>fii0~?pmWxx&0kxcW1-vSO3$z2kj!X}i=sdV2fk1nk
+zGQY#RN(eP%KIo6ohn^NF>6gSC69+wfmqU-|FGh}~y8|;D3Jn<s^}d>`U!;=Mn_i+^
+zPa8ujoR+MPCDfAH>JOqM*7eor6~;1JE1gg7rof5-P7p2iAw16(qGA)rZI2b7{=^=K
+zUl$L2tgk3tXN^Qf7rx?eF(t5^Yf{5pj$(v>bb<ds*es!N*2t+L26<~XY0QRqtn;_n
+zfHkdd{dB5_6n!6hi7!K-jZa^P^j?nbA9E{<O_uMsSEt0Boj<wWj}PAT2Zwt$Wk(t?
+zJJNtw4$hwJ8H3lx9$ycSBgP(UE`V*dw<lK*56l_CS6*SA%KAw6G5h~Sl^o~fx<2XI
+zbEJLq0+q%jyt3?hcc#@dIeV~X$^GTi$(C8YJU+dgvV<Yj0=RiRc-u#L!_<kYIfi?H
+zcC!4SBd=X8`Kh(PfSLKrJAhG4PQumG_UXm>`Z%BI)!x>LA#@nRAnx+CYhTG3keBsC
+zu3GeB@8axYnZa%o04^E<GAZ(6qJ%+3KgZ-|`_<FlAu52Qr!8A9kKMmc_L$>7Wz3RW
+z-(-FaX7TO^hbL%2cnD}3wh!R=QEfMOZo&bWSnZ1&`#APO-_se=l*v#9nB_pWrbW8X
+z@Q+K{81-TGL_doLkXoiVp)I^>k_XS9p2?pDlpvNCfru+sit}Y~XOxgpx4b89+q9iM
+zZ>vu6LoS(Fr#N5sM|*e6tN5}Inw^%Id)ImEeS)~uEeOA^x_^qg2iqfvJZ{*Tpw9md
+z#jHthM~9ke=6>^Zh}8cJi*v1YzT@)~1fQRAgg^L}@%8N}IxD*#+0=V^>jN%ho2~iV
+z(SF36!zjRo=*b&`Qj-z?vutDsen7_aIvy-mwV0?Jo&ak5p4rB?-u#9rw9Jj=tK5Ao
+zheVkQrCc+xF2!2rQlCnGV#U_!Y2EUU?+5a9%>!*$r*nj;54%Xi)xV<Lco&QuHzNMU
+zdj)^rH&7|zBGX_Vhc<+ztbu*dsan7uC+5sm0?39?u0OLHH&mev1>+6;ldn^(hEM5k
+zf1BzFzDd-5HlYO-;}g`w1!zWrokKYf5@SI3Ci(M&$>Z61)LYjkDKJO@<wUqUxHX;s
+z_a9-Lf`{<{gvd9kWMBor_ZUUR?#*@}x<4G$?ZlV0+hCBO?y>APeLcQ(eV2rAOWeMP
+z#%58YA4NExjK&&Eo$AHwj5NM5-q<i9ZX`ZWucGzXwSP$}DfgLW<`^faW@g}X^Arwc
+zaTgy8Yrnp{=_-4ADs(})^?Uq)6KFf}Z3$d3bOSN<(DGP-IHuKq%TUov$=5-AV$Ks1
+z4?&HHpg<wsSoA6b*c0H667q(2vCz$sQL>jc$1QPI54OUTYaG!Ku*!o<mxCClxtw9y
+zs_s5jmJ!tuf!v~EU-SPmLc1z@UVie2=Bru>sfQkID`bsCY#1Ekt8}b|3l8OD-R7ZN
+z0YLAxGq4ksOcFuDg(c7;C!Hn2gP}2<sUeON72zYAVkhni#Y+Icp<hN_Ntw4!PHpG6
+z2Ksh8iKOm`!AB0rgkFL<;vv<;r=MCZ7p?agWg+lX{;kk0`^zC8zzNM-*Uygf3k7qw
+z4@Ak(zDi)Bb6@%lrnZlVwb#ll>PX=fKc|;!bkv{9NN&jRDpe#k5yJi|l`l)1%+5@K
+zNsSd8trW@Y<beE8V8tRr8GN+V7Nlkb*>Zu@Ks6z}SF$dO&xyTV1Y)2T%p9f+X{2$F
+z@~_G7VAPJG)rq7aP-uyJXSJ&WbfcAOKd@>$6Txy!*Vzg2I=Bi2MXS|6kCqjnvZ$s(
+z>)Q@y@idjV36)?OBTRo5nys)d{sz!j(UGxBYUT7iv49(%+lKw=d;Al0<$*s)g{nti
+z(2ummQ+l;*nqO^_DYl`EDn>6!F3#Kl8U_t0)N7@QLa!?~A8YL(bZEdLp7>aOVcOR4
+z>%_to?7-#>$f6eSj#oJcJtK?Z`o+{|Fq_M3Zn1m$GDUnbu-do;_E+d+>Uvltj_@Kr
+z#Mfeh0C34^IWG)wmDw{KqfEgTPi|(uOog(WOK?5+S0fmcP+3>+U|!SVadfzdn{-;x
+zv?{;z`B>7pfGZ0oJ;yMnCNKfDlzoxS3_Z5Q<WxFD4z44i02ff~^IlfhUoM;=VX3+^
+z^i;y=G8Ed$mi=`X8`%}cNMK*#C-U1JB<uJhyH!wYp2|E2=^#j0rdv;o@mF~(KoLah
+zs=VANYA+q(4#@DUKfJlDB|-A0P3^dtazW+N_XuQib=Jf5cRk-GkHJ4ax>X|wv;KBp
+zltG|){~5wScG<{G$~^OmdD(PLQ0aC<_k4&CEZqjTeoQidYGo1#W8ovqLv40AudQ~*
+zJ4PIfG7(;D3eS~$ixH^WoHB{%j=6EWMQsyGz1PE#8f=y9`PWzrEaacFAb+MqqXq7h
+z1Tk`h5YJzeB(lT<vXKqzluhxdh8&*6jnk(CpDgZy;J`wu!*v=hFyL_YiB07Q<%(cZ
+zuS21g9PlC-`?9&y;0GuwzckwDlxuHn6p>V7j)YGrX^uIhcN=637OfTJrN$ftcdfa<
+z`#P>u0g*XxZOZ8l)BRRM%zco@fSDE@WAX{I_IN*`W9_P+)d+xUkk<M@tpxd{6*sz3
+zv9Fj=8$pAHGSx#yT>g51DcDCgUi&m!ds_iM;;UwlHq0lh7c9W?pewLVkjJ60DRVwD
+zRJj+=P35Sk?j`uns8E*^>yW$ZLa!6xu5tK)M8l{ARNZmvb^I|%>}AXkBbM^Y8k{#p
+zkkuu>6V_fnZ+nGuUF!@^-&oQxAIl|+uL;WX)|{YLiMQB41ErPZC`((}Q_(TYMp8nt
+zzAS<#&s4#BSWcZpav+Jhd}mrS%h=H6)TRuC{K{B{5sUhmp!e@X>RR<5>L(0vzcDr^
+zH+@hhTbcv@t^!PkMwRSX@y8v`kS^DRqseFot^<|nB4aC2M6WWP(b)h1uPcl)xwL9u
+zEQGu%0R^g@_JN8eM;z)YXQiU;SVf=u7+TDa;(Ofl=oSi@=7?)I4cJ=(S^_A`q=G{C
+z@{+sH?&oAdV83lC08iTn$>3u%-AAbi<?HY<4kLb&fnZx@u2G9YFE0yPcg_i?YO?i2
+z<L-j+s%L*+lgkgCZ`Q9U759b=tJY&#_Nb?Pg>jHy3;2A+#_HGEg{zqXd6|dX-BV##
+z-M@yw{*lAf;2bwO9_L9s!}OC_JNOyfb4MudDs#N0d}Fe9wOFgdv%%@MJ&abZa`Roy
+zjZJHx?t@)zQTPgM)xFE<kn-@?t7DTEsj0u3M4O)otsBHEo_0CNAdou|O6@8|x(hx;
+z5lm+#eFWN))4?7SW0_l>|G4HT{T&t=nHea>(cb*W*&Qn#;(M`|e&UTh1P?}-6p<>8
+zOs3~$P+SL`-lC3)F4YXA!0#>xmNqP%#frEMicy!HVTlbOSPp4y_b@Xt@R!g^7;d^n
+z1boCxfc_F!qg?nREFi*sQvAYC=A7-nu5YEqRoX)H1WI*~`ya~^X?ZGj(0Zb6CTS&Q
+z*67hb*l+OWSq-#oXXRnL(~%~^_uG!M!tr-9<(Eiw*k;0ee6|!&t7Y64=nLV<DuXAX
+zsCRw}1jHHV6L5@d5MAVXsV;Fkr>tTvPIdbh+@OPE<coUL>9pj#C9N}{!&@fpmX({m
+zNhh3uW3*7GiCGJWRI^Ti<FQE5ZEp-0YU^Vpc%fR~7_{Whc710eH9HV2aYgxk<dj?{
+z>~JW--J>RKSIqda_>u}fNyMCz6Cn<j9BeF2o61tO5e+{W9$4Gvl*R{{=94&1PI`9E
+z=HGdi`FFb_qV3Z9R*`u3RIss)l>)&|Q$fBSeIof)$C62W#3UB=rR^cgP1di!o3EWW
+zFB_k!qwu544zY5lte7sz=A7#l^JZ|&VXyrUTx+%=&B$R2DO-e`{hXSe#M4M4gwe(%
+zi%LNoEHY}dm*v}Us~Sa=Pe27x+ZE8K=qM*PY*bU`*YEZ7=J|&dTtcoZKmk3V_VL}2
+z8WBNVj(I1ZKdI<ucRHlJa=l&VR67J8tHQ6Rv_)hjcjhS?D(D{7uH1RvG4)^-drS<o
+z$I;&D&rU16dvaaHHMb&_;7v1Ji0-ZMN48c~alDLxS_3kk883FvNzd$JNW?zFNUf=q
+z&)s<*Jr5f@law0TziOAM(VMXP-h|FiNqNS9%dzB+HT}6t?V?<3W}6Nehl`xnb}xc^
+z+OEo{_Li*SH$7g#MY(u)7(F<=y^vht8<KJ@Fi7<Sv-VcF{t`dpi>K#}Uo7TJRLyOV
+zIhfi@dB_|Rd(OK4JdpSDVRb<^+OyrF=B-zpa<y!s*ihRye%h@p{L?BS(IR#?zL5-n
+zI4KFezh!`murL#_<nG12Qi_0o#3=buu<F;ZiHFJ)y1DecyFt;NhQ@pKP}*@PxrSLX
+zF=2j8I9EG^2CYYCazD31p;}|~x?VSVIm#XKNUeI@K4n(^$Ks*GVW<e^A)~65GKZ$B
+zRjKJa{N2r+gO+TJEbFy?DBOd1<Su*Q6V~;NTf&&?exZ8y+#%7p!|K<ix3g5I*eZ>k
+z^O{wIR>)cvYcgvGO@(X9=FzjD3<B3B>mfpR;D>Q?#qD@=1=Vwl_Kp-Qw>2yOyvR8d
+zulRSts0hH8{s0&LO`O0D5`gDSM1lv<LDW%C0A8sq%rL{pvzGPTfx68Q-Z;<V)JWt<
+zaH2Ef-Z`ulq9HiQa1M0$1O9r+&$yz9fRoWM{z?jX@ywLH(h`CevpolbXdG-?J{uiE
+z2&<AFTl^LTgzR|c*7k2w3?0F?xqFsF?M7axdrJbtvsa{H^f!|<tuDvdZIIC7^9=m7
+z+dqLXD|ebOH|zMb5ItGH=?-(cKmeXCbq2o~U#EF|hIB%2If~Iyb;?D<Gw33e_>=6(
+zZ$}DL@N@5ou7*~RFsw(lD=Mx5=TC;do<ty8ovk;A&~(B!hPE?%nvJ;MjcU{1OL0{0
+zosH|P=5W(Tj>ZfuNHygjs<oD4RU1}ujAJ}iEaP9Wt-Y~&%|$EBpXJO1vW{lm12|53
+zH7*ccKgkh2K`}?`0A8YQrCisUR{guC&@~*JYM98Mh`IzAu5Clm0O6m(4Lot-<d>eT
+zV`d(lQt0_Ryz*Tk9xpfg-D{PfBJ=fpj#CMdX}5>Yc|CaKEErnh5T6>`fOgp%s9ri~
+zYJ}zjqNcMCo!=|?VmHkE57SIT){R1|wt4+Qvr0A+GTm6D3REwLhVQ3lS+#B+f%s<n
+zo^Pfs?m!o}Y&3}wCA2+Lg3_C{rES*m7INMVyqm~!R>K5%yP_}0)GOV?RXIMkCfz*o
+zuk`buK*{B3{2e_>%eP0r-_qH&V^%D%*tAddN~K6p{-D3qT*EOxIRkIcmyW5~v6t6~
+zJ%%=U{tQOkza_T#FfO2%G|-NkE{qcvgYuvEnjt$ecPhjAMPG!r2#Z^!a9#R96Z<ON
+zwP10}^EcXN-XaH84_~osxAJYyspr>+vQk@v03Vo@(O0;w*oBQ*&S%tYshH40$Y)VS
+z1l~~Vi#1#J=-yn?`BN1uGvi{g>Pj(I7?NT>^<nuRbbDtJKOMhP<5tQRQ1cPNQfEcV
+z`3<qL{s`tUT6~e)A_X^y-E`UvQ2-g&RaStG4QBt&8P-4rkqL|oM%vXj9@jpiZu;Fl
+z&1KgYI4_HLY7h6f8oj5=S5<4pBaFcMUoe8F_>;OT19$*c_N$C9+9+<&Z<0$|&JQ-?
+zHxbM3Bify~JB9nKwd*4bJAlhY8{?<YQ{ayr7SdvW5#SnY=7qgp4c5zEg()M@CD6dD
+zIXDb+sm_2-z*ts!X{@UE7PtMf&H}8fOG7R65cOEPPG#&)0%ShK7d!??hj=MSf0NiG
+zm;K^_63cLs=?8noDk<Px@K64tfFr@^=7*%oDwTVT6Osd)YM)gcIOSa7P`4VW<5W=v
+zzUSeaOFyh#HXnO^WtL&mtx`jH^f^?>IxW`3%vKf4@jAN?Y&O=nN7@n0C@RWCi4`g!
+zI6-bYdAGyQv)Q<-Woc}4tTjLWT_6lM?}s{wwR+L3a`@8{5&#Q9$*nSKam*x)x<!c8
+z++dzw4B;zq?q@Ru!1w-jwOdB91t`0NfGDr_6lvv1K5FePvlNIh7h%u=fTE1-g96o-
+zp(DTg5kuh9ueaK+$QpnKC+%f<skvZ|aQRTad4ylKj#-L%G687yTo}tar!Nbafiz?_
+zJt}~lzf$$eZpw+$!h-!S8=S{(Drlb$OiDr~kj*`e88k4pw?I8gJxzm8r(~ixi$Pus
+zdX_6ox5@beRTxzuyyjOp(hMCz+!SDiegrVcRPx^IrJs5=T$08K^Id`TSzQp}POJfg
+zwZykun$i#E<*7sdwOi#ipUh(a8#KG5r9Tkxs|d=0GE|yLCu<yS_zolnRLSH*po*y=
+zN8mdJ{xdItZK1tn)L1APd8cU#yo4WogY7M8lYM8}@g!FUoLRh_+;<IOr-{ZW_4k-(
+zV;D-9f#kviAcsr@ZRrCBJeuSoM`fY?hslvhDxvDR5^2?T5H0TV%Tx5H=lGU}4GPs3
+zg;x{r;qyluKqDpFCze=Ha%na9X-(_jgNYv`hSgc^&j+g8#?{8sJ=FL3LU`?RMtJUT
+zWp}$Bkyzb2&{SXFN%N+GgNNNH;N3z`WH(;C!kX{T>t&)~K3hL<6jQ7e=<W5?%ZLrh
+zMUh47x^j$w=8nF60e@oH+^_QtE-sW~Qg$`N2}EekmHNZw8*8<Ov5$R2Pjwi%0o((f
+z8qOEH7l^iy=EY=~h5F5Y6&a(|pCo<q6A*O*&m`7!{UG-ejc95~el2x=FPc11dW*?I
+zw2ooY+GDVk5yabM*mdWFc4-ho%>#^5w2(p}3k?jo@|A+ll5Nb@WdF+I`ox>rWuG?4
+z3)AzT^H$EL5CdKhuuCF8+;2tM3>M(FS*f+qF_D2&o->@z;e@(LL^pdTj3u=lUWje$
+z9Srar6kR8rXf0+d3<@yri^3hu(I6)<6@jISKr*Zs{>vlZzx0~u)x6PDQAj%I<KQWg
+zeiR8>xG~YkYUmk5(O!%at<0fQSZc*@FcfM~w(E)Gz~|Cvcf?V)<)c|eZAIkrdaA_M
+z?-YjZUA=zxs9@+?=CrIA`M5J2m1qlVXZv3O3w}M)W~td_<ctN{hoiw;BEel3Qd4GS
+zZ6yJM^Lyc91Ucmev2wwDEDs^Jv{$Kc%)hidC^g^5Dpy7R{6TMyQyw=bYt~iqk2l5!
+z{X7)0znZPP1oXgP@+@ufV-a=+gULUsxQr)|4Kt=Vg?(3jYywcWXXkd{J(A;tXp3sy
+zBX?$^cNm@a_K-8_$ztbA#<6Pylt-;?kYIJ}h2aGdxrD|fPNNpV$G2g2LN%G-;(F<k
+z+zIGS&^EFFpB4&?HS*#K7A89XeI87d94AaN1XVA56E3r$E>F($>u=yQU}C{O<my>?
+zMx=OF7`kK$<w9?~DdbgvuF!&p42FJbc+KuQM>6)vsA0b|iGMnudo~d@k)YjMIcrO1
+zI=|GwvfEGF_onLia<F$s`R&NYiCTUPv!FTv{>%4LbupVYcIeh8RVXZwM6dJguo(gd
+z%MR$7w-(fZ?3|DS#9g=r^pZK~m?j(f+cwwrtxzGcjn~PqizZ>XkbzxW@#t-k{YxDl
+zRM@{elpbFX$U7k)6WTFJa@o@D;EK8HzjcwVpn?U%;fNSedqZ>Jf7;YD3lA|V$RwT+
+z$E)5`%VLOrdk_K3x`ccoibv|AwqZj-Z*@cLET`KqI86C~<~-nl>Z~|Wy!re<eE;0o
+zzWHZM5f!gHTuJg*n-AuykigL*yp<P@_u=aiW`R#?{Q$xBd8~}Z2?08{bG;EKES2;p
+zGL8hkBKfsXTa`qF#Lgb4p$KWK%J_EX&~`INxaOywzvc#N-~>Eki~BE7ljPAetrnjd
+z7gJeGm7X+q%G*!$*6xh*D@n1Twa3X{hKosd=2RmuMiS0XLNL{7O&a7OVc_v1vDTa5
+z=HQ*kWv&G~G<avPC%N35emjuBrO4N<ilxk2d7WI3RM2KixM7XvmFk(ci$D?^mg<>p
+z|6+*O&sL|0orbF-Yh!NNH-^&RJdnv1IiEht8nyx7?V19MIV_`E;~kntV-KpIF2BP1
+zlMTQAv|Y2Ts+zL=6QDg>&hZh7Psy-D=*y#t%Qh{Z8>zrs#-p_XYU>d71LElxSQ9-4
+zovv+moHA12tER=K(aemad3ujrFCdLAPcG`sF}zm{zHm<FVg>-eAEzE-!?b=A5&{T-
+zU+;79XcJW}{@q>vd`VsCV?s}aTl@WMy}g+h>&TeBV8SsX)<Zd|-S;ijX%iLRGzdd2
+zIk{x|6#88IoX<!o&{FBie7Tsc-Ygq~dr-0D7x_r*`CV*A&5C6m;Dcc=$2O*U9q}+*
+za4Wdy_`RdzGaMgIZH};FJed*eae91lSk&)sT~}e>h~XrnOH8%$1NnV?$TqnbV)b%n
+z&GMe_uzU@|;8z+9wx_a%j4qDkU<kKE^8_bcywTqS`x7f(s+(iRf!&#%w`^Bbx<x@{
+zglAoCgARvgdGXxG5!`b6IuS)WXI6PP4z&Ij&rCfZh;p=<X7z4*p#m{424Hv__b^PS
+z7KVxhwfuXAv5{V75^ZGaNnBl76j<bun2JPN$RJIP`NeR%a&qi*q$U87;i(7Wuh=bT
+z$H3w)ZwOS-J~hBryIM$u^}&QmAqBDX2NM{Cr~x9KmqQc)qV?N%aVsM?s}zjx>@bHO
+z^8DbO(c<~Z;Kg>oxJptPk6fB~mIby`lF8gqnGnVO()eu|C1!4$t9sf8@w**Ji!^ir
+zG9g?+o=G|Br7VTQ>=Z}*NFCq~$t=gK#N6x*Q5K3#`IS6XZ`4l}WLiXKO$aJ`u8R7L
+z*&o&<nj|*VILZoF&GWSJN)F$4Yi&oSLm2p;r^P)L=3oZf$|E+d)Dga)C-VS2DEs3#
+z7fC`!F4?XV#2qBg1`lXKI;RSnSDgUL_w0hLoM}78Q|Ecr4W*euarja^X8Ku)s%t>`
+z@AI5uEFqY~W&ps4hE7*Q*ryW!6g#TZCgLh4s6b6UOEJ59KEJq@bd6N*2qJwcSRtm*
+z9yYn0?5e`%6{}C-a!qVqE*SD5!n-p^n-)WpCyJeCyENT!zB`viTH1~=^T+0oQ=2=>
+zprs|qC0@%;D2FRM6<E3Djf*Ciy5B5FV*seF7U|hleUwFS*R1ItT|UR7g=ZnEJTP*C
+zm$V>eVd>?MNU!XA@0#5poecE;I<+aBMv)6j;RSI<Nk`$!By???QD{6czHF6&72x0z
+zA)fDeoE(o%#EgrT^?A=`$+~X~(ok_Bp`3K{DV`698ttNG2Tgd13t(og5^U@9|311?
+zc{5Z|-zpRkoeQTYKRORf&_1FnxvniVnpvFc+TzY71LWD>Fx2wbgB+fgM}gK@o|HgC
+zNJwcHf|K1)<&v0Pc!P1xQ*K0y71%7J%5h5&=q=^&y&SqwPmK9u3Bw9LWzZOO8^M8F
+zIi}Pe3aO$A2MdVyj8O(Qye0V{k|doB7KmVsFX;E#sFckIQ&l=(d<2|W9qai=xx#<m
+zfA${}wr)Rj0~r4YHH+HED8^*q008u)004OZA^rQmsQqUZi|9Yu;D6UrbgA)w+AdD@
+zE_)de9u!b+2uv06P3J&1wZiccOvAJrq$Fr1E{8uKvoLvyCGRXcws4T}mXJ7v?7P!m
+z*$Z9jmz>4|mzs?0H<s4wj}stS*4|W<imjaDUGnQchs$XLPb(_Y(FLE0BZ@bivB}h(
+z$r|IFl8h|Js=c*3J>6K+)Vki?`gepRU!zmX7yF*5J5rnzDBAjY=RQlZ84z{s>zXcL
+z$GSx;ytl}{u~kK-NrTjw67J2-E73YL8(JE#ir&*X9!;jRXP;Rh82^5`KVH!|+(oK_
+zuAjA=+6(L})wkcy&>;PZ8JUyYInI#RX+-CH#x4s)*EztB+csw2a$FkUF(C`0^xuf8
+z6&MF3qpDUiQKC~7Z>Wj_ps4qpVCl@%e*;hC*5!&0Ebahs<DNI`EGYIUbC8}skJpxC
+z+p-3Msg82OM{(Lpq^C*1K(@w+zv}nrbgOFeZL0tRoYRN4$sM5y<XNE`1fHGiXKg?0
+z_x<&oaKO_O0RwY18yQxz=yz&>8qUxz;=r(gEwTCE5lGD(ZhhX13owLoRiB6rUgt`b
+zjk|jW*KvM{NuNOkyJS4bxcglOPnvq!Ol~XP4+`n3E07ARCIc#K!z<(3k7DpYWQ6%-
+zHF}p@Q9K5ma6VY@e50|%)ZoLR3TtKSIY-`l6MScqldCmK$<&+6+?*KA_wDf<s@l>j
+zpycl<9_lMm9??+rUS2sYxxkZqhl!QK&HiK7WqBc67HSDh)SLWzw3=ccnSC}(dizPi
+zZGByr7+Am7fQ)Q$)G?`C<McrZyhfCX@C|GM%o9HK)*(vn&eMlI?s=7TcX=e;E{!g3
+zA?Gt@GLSUH2bTk+uKi9r&|Y6qc3pJ(03TekiS5pU4ruJe-q{5)EXYI0P%%dSl2ItP
+z7SXNct_>h*R~tdbxb^Dw&gKyzh0nxOz1FN}l`Va_@2*2dKo$0GPE-t6>wGw}nCwlc
+zjA`{R>9@h%4~oj8b|HEmUV}2!jW8Z?rpBz|f0^lO>jpiVej?NtD;9cJE%;UY&eYyN
+zY#`Z+W6n<W_!UY&8ngz5xJBZ9UkX8dKPb(&O6zzau6beVf#ys;vi@ouCFw;?O%X>;
+z%qfsYyic`vUBY8hrQy+v6*j#K_~^^#>*=DK^kc@6=#nzdPLXrFL0-G%z^I=FGvUq`
+zldNOsh~gJWVS@rTDgnX^Ze{_qE!oG`+iX;N|J#ysu+IDu-;%f^s&BuNSnhdz2dotx
+z!h3Khj$YvTOWij7PHys;9boF6uZ(-_m<<0`d??5Emh6r#OE4z>MP3uH*8uOs&FP!s
+z0cZvWCh}w_?ulN|fZ<kL;6}N21)0>tZ$~5!P^6=C`3g=zPA{xA5nDH~t;6NiDtru8
+zg$~1`w~G>Lm<o-wPs(S=k0}uJ#vC^eBhD^^f6S`euK`Nmo@n;~L*<Cv+;##uLX5kO
+z;jS3yrS}J6VOY!6yRU*6MbaOqb!arHe@|NDhIN8X>wW`hgfW*R5MN|_wTB&5SWFYH
+zh$%6@pn1dEJ%5GSq=hk&#>697wD$(2uzyDQjAnyAj_ExKnRFu&1O)K^BkY}`Y>T=r
+z&9H6TI$_(66DMrjwr$(CZQHhOTNPPZ*{bqZ-u6GNwx8C^o^#CE))-&!y+NZ+XvH={
+zvgi(&b46D@kP|;-!z1a33(mg>wMsbD`M#@<XBh|!D?8e4mw*%aG)7!(@&RjJmJOp^
+zwtT_%m1?DL191oZ;twujzw?%2n-GNE5y!V^$d%2PTp+<Tq2U2-p4i%h{K@ugrVH0L
+z;6v;huA^jWnxPIm@SmEa6b|giY#<EEE_MiP2jteF9_&%6NYWyLTzRIz-E;{6#HO-m
+z1{j|`!z=rjsAlwU4(2)ADXgllLbmC^wu9VMdxXPQ7DMNjA}cc1oPI>yyKXk!R$IWW
+z@k04a1YwJ|91D`biq+uCd0GA@(rF|S-1Nc65pNn{vBF$uj9qr-0REi<##2ukX)We+
+zXyS-T^YkSVeqMMs0B)69JB7T*Z2SGJ+RXLh(gmS=;>s!*pg{eKR65A7RHN1!lQm|Q
+z+APJ+K+@e|@IP7p@O;I64o9OzzTin|wEN-%0`1_J)bA&d;!D(<fOQ$QLm_fjJ0_0i
+zN*i%Z?9$By94yn5bvyFRoqK0o1I25v{6nsv$V3!|7BxJ!zowgte8~ow(jKVSd+eb0
+zRevp(Ya$Ii^8^G!tzkr=Ofv)U>S;LBzEO<^8MLQM*E!S9BJ1!0l$FJ=${i;GOV4R=
+zQ)I4WkN!F&M;)m?3P8>N?Qd1%>K(zC-iY2N6&3@137p1-X=+-5%A*!FdQ5fjzYoIR
+za%-MLxP5*CTBLDytrP!%6X30{dK8oJ*^8DJks8a)@A(*vbun^KrNR*S>!-o$#Bc?o
+z#XJEX&s2UZ2G9A5&qT$e702O$X%Zv<g&i_j%51jz2CtGv0+Y6(4KI?_Jhc<MXyDtZ
+z_W1w7ul|SdahVtqh5Hxr=0N`M@8AC+NVah>vj1Ns*8h<6{vYn&B^4`+15t$UYn?hL
+zh-Qr}vmRW^;=l+FC?}|t%@8F2Rq+B<bhD#s<+i~2pY1jDD(9l48QJSyDqG93hx=d4
+zoJh6=_F@sl5knDc<C}7Om8D)2#JZf7OchUOt~A3=D#%f;_yZNqSlA=@*l(xqHz7>S
+z2r=D!YZ>oFc(o+|2g`Vr#_F&X{q2Zm_6&qhXVcY|92J)w0z}h1>3^f*I43mdcHA|2
+zqmdPzhgb3Sh8lNRQlBR~)1+&H_XtvkUM0x(eHFNf8U!jU_{%35Y$_`!_|?D3MHa~>
+zoSkrm-I+R&aE+Zvi4jenyTVPj*8&(9LxW>)9%M}R1SmR2K|b?TW|Bi>UWIM~H&Cxr
+zoVEzCCZm-YLUqPv!?Q~?3L(_q@Peq5)G!n>;zhRzU~>ILcj0^_O*-9um06BV2ds~s
+z;0@DjUVy*wId6SbB1BWsSh(S!Ix7FY`V#n&EVm0`)m^xzGK}^}FtX(zVywILC0KJo
+z%Q9O#GE`({6C^7+=9<eSMzg#&8h6+tj`-7=JCtl^_N-W<w)QZ%k;y*Zs7_Tc_2j0^
+z{MNxHgt#*;VsT{_m;!xTcJgIwl1MZm^a0|Anw+Pjp}lhkD(eR>?4q4syDja{x9bCA
+zFSjrIBlm&zw(_|vNXe<#MT<g3Y~y|+&hUsm@2ffz2YAeS1>8OV6AUInv%=fMSk(3E
+z6A{@9^a!N^VQQ_c?k5`h5{CGPc&3`p2ER$c^BYsqIrF}K==#htzu4C?${a9M>KX-L
+zT}YZZoGGlhE;lX>VL%>_znN3!x7(*L8WtyUjlW9G+Qyu`Xxs)Iqa+hzknT`!A=UNE
+zaPB;yb={IrmH4Ahv_eUwO@A7{l)o7IBU_9Gge5Gy*)my|AMbhdyKdxw$-_A3<*tKF
+z5j78AA)$e2i6M@7>DTIN7jg3<PIGw>!y!G`YD1?Cq^ZN~xCy30E?l3W>{N1PUj1f{
+z;2x87a2p(Sa_rzU%lkX48RH&>3kCM^;7)S1v6$RMN@G%AH@U-S<a%Y=$x3VvrV
+zq(Bx%PvQ~7+q-p`BZTO<FasJVY$K)JH~bS`UCqbwl3qFyCqT+oxZc`0Oz(Mnf|2Fu
+zAx|U7tKuQd-@qS-7(P?hn3?T5?h74h!y8BJ=fPW>a36=k_8mn?u$2w@?>HC3ZP8l?
+zHQPc>J-2%^&toT6(I%a@kPC_2_QQ%|=tV1}vOH9FyxcAHA`RQ=-!|Ez_Y2{l^RV>c
+zZxQ={)il;7?5wjc2L?bctjcdxYK$TAX;nsUekzuE)|sE8#2*d;v%k7Q<)op?eHld`
+zcIz_K)_;qIW|s4K5;F)7j&xvFO(LZ@K@azJCmHdO8OvgV-rfUNRvQBPKov<8KD!(&
+z$V2YUDvt-MJ>5nfdaal45IuRQ^C=t$-$cT5!Rq$E97>!1ZsDH@zlW0k|H<~@e|{AF
+zN~0Vc?VSu9ZT{1_bcJgXvoU=4@dY(pEpS$;Fm0_z;y)vbO(NnuOpgF=T2Trkr@2*E
+zES^^AshsfnGCj%LDi<C<E$Xjq>$qs|oZZ3H&>(e8BQc*Z9)C)eKH?*R{j8+KYSpFH
+ztb{9<|N7x*#D>W?L-cwoqqD-&_-A@r*{LZS<2(HRsknUOG_xg1X*j+5N?Oy%Y+~JW
+zN^!tRyLgImhFpG%OuoT_Oul2I_W06LtxyJeO;~KF=t(2_>tJT*E6O{@WN;Hq*9cvz
+zSKjH%rr8oOmcZq#T|9Z~3M}#dp=PV<soB&49zej*5|va!``9Wz5=*%f#O-r;rtEyA
+zK5${_mbx^Zw#6x-$ApJo2D)6*T6OGP(Uw6cl>pNqBU%CAm5df^*{;3QXrcwOl)K5z
+z3&X1;d|gAifg<Q<8P(Hh$-cvwls=(b?t!R_ZdGb2&-kjD?l%G+4VD|rWzbp`o1n-N
+zvB-#w0r0NW5MN!yx!nDbIz={QW=~XKj)ZJqH~jOq8!bUbdmDwO_hQAU0aUB8#Y~C@
+z$UM24m|(ux4YEA!Hvp4$Y;lcX<&ghe7iB1lfCi;RLdw?-*k|KZNvS>{aA?|KLpD?d
+z8{8XJCbX7}w_KQU^J!0LG2g)@BLJxrc<FGPDYC0;Quc!!z*~9OY4MBV53W8Lb-h%x
+zR*efpHGmfTXRI89YJ1NbZfbin<{SYp6I6nuXr^%JZFsCHn3BN+9xv_>@US{Kx0od~
+zE>Ap*yrM|5ZUu8AWtoMs7_!YdEwtqB>GD3`6!!5jOeP$1Itp@45q@Pkt@MFa*(?7I
+zI;H;L0C2TAPKr}TB_T}I%W75yv~d0zg<jLF{*L)VM&Vku@*DU^^y~l%K_XqIxbb;^
+z;lT|<ia1vRBZeT8NjjlP#_t2>#-`rxAaRgIUDGzPu^}H~f0^SG%t{jM)+bqT(Z9h)
+z(m8R{DReC61dF&^WjCS}hkz{O-0JOLpG}2Xe=t4>^{3>+4Tm|`-Sij%pzr%AKB{%*
+z-8&7<>}woG(1U^ulGIHiU!*kn7KU_u+Y0lu6TBX1fxmyJC<E3IpkzVILAc#!HW>a8
+z(Hf4Bs6&*02EhH;yVgK#PE>z98bVl-@2euR#b&ppD1a_n=dSi-Pq0s#!C}#!LSPwL
+z?wSGkmKuU@+tCP0S>K5DAFXmDM6ikmASSi@fs_yX^N1>DVF($HXYgBtsQt|qV8h2M
+z!4$70MmcnXZ9nb5oC_=LJAm&}Hw2#jYo?h`RewTFQ{U?zxriz@pJWAZ-SeXb$<?pT
+z5N(j>n7p($AFbNJB!03)Umb&+NMediyDV>XUQp;A=T7ed?Pt72>eddGE6L1ZEI_5_
+z<SQiE(8Z)t?$(7|O8j1MbQ|1i->h}_llP{L#<rnqGK!B{!wgnId|9ka-G~j2+-q>p
+zqf&O^xg`B9l#Lqc`iRN31=4#VNUn0-$5PA!7T>b4`8<_}S3(}JrDaOqQue+#9E!Y?
+z<7}Zb-x(atTAl;1gkz@%KIU}^Z3ohyv61&NR&qi^Pce7@tGWXw$odA4bR+j>?%&`m
+zqkqUeefCp$TxQOZ<G-l|_{JQ!*{V`}tp^nj?i&K28Cl8FZhp2&jr7O@=eiQY%milY
+z4bRQvqRU%#WsoLA`HzIl1_`5pkdz}@Uoc8>E!!IoY^BxTMWh5y0Eu8(TzEX~<*`rJ
+z74CfX=R5VcUVlYhd`mctzoDqcc?{GJ&Z*XiZ5f2DWGmi{?;r2)&m!GNzCSm0uu$`z
+zGT9gA=VLcibZ{t-s=^zs)Xv7_nWHMzyauGfQ&KPojJ}GLc#f`Sh$~GU!?C!sr<X31
+zidQ-E7*aNYm83e|6(gvly@9SN=C+Q%ljzhYmrG`J-$Pcj`p4n7tifp(n_W<=9oTar
+zW-V)Yniu)lZviEvoid2|)blu~)1jG#6v_EUj#RoRj!bOfihVrOfXyM2AXb!t7$A;_
+z%d{t%7M&=P7WlUBND&?%k;qc=wEn@s&=*KeZ8x;*5%XbC`Iq3TIv{)XKRb!F+qiy6
+zBEG(@rm=Z@E`wo4bea{IlV+qFi1iYCvF6=1<q&=>c=D0vr+Rg12{_bUK#axkb%$sF
+z0-_zgz0D@vj>PZ^;P=8YFN)-FYTWn;yIl!qy{%Y=Q6?*RdK4v8Eh2j{4d1CfCQOg*
+z<>7&gVjlKTw%9kS$TO!{EjeWCS@lS4uV`7`(Be$q3)hW;f7_27%O%5Rt`)14_mh{;
+zR&uTXV)R%8L1*&fB9<0sb9|+tE#X#(8Q7Esr;ZAFjD?3Jn<xPC4$Feewi;&;1=V4a
+zyJC<oo0q)FK>m%1IvaRMoB*AEeeF+Q&mRfu0e?Mst=k&fHTpJewrxi$lB&p<HA*@8
+z4Zs32!`8Vwa>8Ms1*ps}kmT4aX&54L=iH9C9soSe=6={qXonUzBuW?7QDxs(t7Igw
+z_#x;SGXwUF-eT*0Ow|X{E~8s8Rye$ZsEtI=-u7E{hn*&?&#G?RESi&K@fi#v;rO@0
+z^iaK<2Dytf$3-vv@JZ$FjH#HQ252APt5Vbte0)R|hs3St(|M?PXfCk*aasOhS6$6~
+zQ%x|#BHpf5TtsA>XYIB9)XPL~bB-JQ(CknDoh;z%(=w%o-6g3bQa}`vT+{t&&kfri
+zs3c#MGfXM|@|fWWr|{Q&#**kx=HT{Dp9nAjG*;KgXnLT8xMiL^Gj?HTB=OLFj=bp4
+z<FS$YZ$Lt~?2?xKeE9v4YkD1NHhP5oq>0ZwP#(oV%pu@-8O89=-{aMTi}>!d(83Zy
+zpGSc-6i@l!Za*e8yKd71TQaHvMYk4(r%-C-8E?T0_k_huuw{hY(saM{W$a!qMpNqx
+z_UIoL_qX@A=%S~Hx1*sH=!*$0R4szfcB&1Hs(}dr&E)I%JkdWUw>IC!TAz$D-j?Vw
+zUIaBcJIWjF_;Zn~;}cdB+K+W|wy>tnlkR)I75F6(9pwtDV@jkUy!bIX@48`c4uLkm
+z5sEoIw-F#C9}#au2ScjNmnp!uQy{g}Q?I1o9&h0E*xAQf-#pxYi}dVXa8(P=Zq#~L
+zdn`pA#5+{lw7?LC?g}a!uxQ~dCBX-D{RmVzf=n@Wl*=%JLR0>Ethf-vX`n!~oW2wg
+zV;!a;u6s`~_n(g_e?&*80w3rFsBlGv_2v#$S(e9qS#K6g1McQb{q96@rQw8$U*hXv
+z+($o}HP!jh9eF*Bhw-za^!gNmq6$vtm}V&9!=4$8nh4`568aa{B2XQ-2YLO-F;N^w
+z&_pH)q?GbmnvLty1?XBE0nrrcrnKXkSYU1=u*Ad*+mIH?`r8+K|70yNMl)}NNR+q>
+zps&}my2YVS*JcWH${{lplEqcHarOf-bBDyzRTCwAW)o8%nv^?X*)qWQwbN#ZxVR=}
+z7~|uzp^pR*e5v^}t0I|`&9P_{Ye1y9VY4B;Q^Ag@)cCaa{!<M*v2QQf5nrLfxv^I^
+zT(q8f_h2X5r@cy|_hwWUY6Qy{y71^8Ald8Mx#g`<!iHBGim49taTHk{|0^<$+Ma`L
+z`!1*1WqNc@Fz;n_nF*oVWTN@N^9E}w!2L;(6xU*DWlC5;HXlSic*hB8>5qA;*Jb>&
+z<{`Y>>Hui`Cv*P00f4Z?Fz4;omDK%_M5_Q+V7@WT`<}fyhtubPL%9zZj?XtYsw(Ex
+zE_C^)wtz$2TLZku<;On$_Q!=<wkDfNl(1*M)@#NIGXnb_epuoFpyZnt|B@q!6uDaH
+zIZ?{6?WSN+(zP!<sZ(082%$xCA5Emc)&23KTBRrYfhESSpnEX6nzn{piKaY<5X^t9
+zQkS2L{~faG!VSYpjhW8nlSX;$Ivxbx{V7`5_Fs!xLNmSXD=>(i|3F4go!qaxxA}F7
+z^d;+laz<0Wpn%gM@%zS$^dNw}Hr6Lb`eHf3!5md9ck{SZ@>`Ub@Y!`={knfR1CVj0
+z5ev;=ZFLPkdqGGi!9{P^jaXB7*Xv|*rpAV|)1AoWT}yNVPeXK}%vC@PRFF*qE}Pdu
+zba>Q6gNP@U$mT1KEMlHq5rowD9thy8qPyZUo@`vN+%lEY82l`CUyh_w5<m#DvX*R7
+zl-iqZJ%ksPh$M1w0?Dm{nSagiyS{S8E6d=s78eUg9xkDtH?l=8v$>e8#*_32qGf_p
+zs}J2#aZ58kv91jkHWiGS2T$2^q^exKkAK(hxvy0vc~T>&9P?Fjl>qQ=HWOMQA6o~M
+zYT>n^{C(k&HZkB8Oj~9<RQ%!e#jha;h!R>?l$x(%ODx2hDQ3As?(VklP$y1V;v(4F
+zz%xg??*-J7&@H>nT!gvzsmZ!UDDN@-N=+!^NItEYW4{QIOx|v3w_tg$9_xfPi%HK>
+zU<r2#nG|N&5KCmOgEynB+>QoUSrnp6(hN?5c2-?464KKD;PGac`K?)T7abtb4QOIQ
+zro=l4^4)s!P_>|q5ES2$xX-r`%_0css8(p&JOF_N6@{x&JDS2^C1AbF(KCmn8qUlv
+zV&Jg~f<{vtM4GFjJ)b622*DYs-UQMn7%1I_Zu15h{G|+d%(ZLsK{YxDN*SM}l@M%r
+z3vLpCeGG)Oa>kCi!B<I)+XyYGz<J~BbdIDt!4t@oUJMU51Zv=1toYC^`#VFeDON@~
+zrwI%$qbt0ylZdFO_|B6iP~NbDz};44lS;vK*UfH5#C<|B=Y$Os?Ij(&@Lt@uuawf_
+zlGaJHhKm5f(;MMs42O1M#{d`3UM1F-J5+g33;o1fwFj(FA~ZQqJ=vIr_>FRGP3$0&
+z*LcSs?(HZY>{VoV9x~A(TpirVZ{9N~<m+_9vlk?!Q>WZ-Zs*n`{v9x{Y-W#?Rlt6G
+zf<t;1@rmmnw<HEjrwX`XK%KXrgP~V%*AE?WoEv9X4SZe|X#UPsny;o<RcUTMAa$E-
+zlTJ7rSVJ;gXt*9j`046-Saxzm(HSA@|Glo*8Gj#NMq5N$)>2(QFyEW&4DS(Nll{F9
+zxf1pak#a7At?>K-30W69!%Zj04B}PSM^Ox}KQM5bQLbH5wT&c_<25YrIW0daj3v-^
+zJTvFWiRxt0c`j`^Bh^E`=#~3uYfPE7a+^g-sSVQNf>HrDAoX@^@b-zy-Ir!OQ`dzi
+z?yFn(FytE>f^V2K16kG1=kI4E1n^$x=dUlyOg_-p<SIt~t&U-P=onO{SG%R;w5+=9
+zO~Jy=1Re<JJ99f%nk=jo5JEA{12{=p0T#k+y|}Yat)mj~)F^k#7VBd8W<F%@em2Eo
+zaf3k(JUXl4mROZHk#XAGUQod3h>e}h0!(UzY{xyx#`l`Wkb2D#h`8m3Z$B+~2<Wwx
+zwu_gXfpMxZW`13H#=W|^Hhp?D908~iCSV+t%S{|8GD$Ivrm8oCu3g5=Zwc6&<Adt~
+zx<pVm23!tZ<5yt}kKO*Sk*<6#-#$?2$PE~ZJS!ru&<$uMZ(DX?dCJ;npTM~kHPww(
+zVai#@^H9#-mCzCCUWo%L5$lp!Cn&xv8%uE+|Ie3`UQGu)czZ+Fk=M;L%nK_i4XkjO
+z&yVaeWng2}<QH~o3Bvia;iqG}mLD=?SojZuKG37E|JL;7Htw3j=&o}=LLS1k@W1!%
+z8(*Fs1@SPRNJ4+eS%oDCZ}T2g&xC6f&c=OAA~YSWEKwMq1Mj?7*<6_sud;dVlR&3C
+z!u+uL{WFxec|!ylIQKt_L+z#zN_Rdmt$gL&pgg{;@Zv9HfWi$zrwfdSm;d+Z_WyOF
+zw6|Ki9Qak)ufqWRj#~e#*7pDDtNf3j|Iq2ZQkIbmq(|s_pmyI_Lh$h&(-yWF#^AwP
+zP{e4gYb>O#zNl%brTqdZAZuqpgrjHjTA22jq{+~e#u&h`n%ydACx)T}E#t4YZ=|x8
+zz-^zNjO}`cJkeSJgY>hQ`k<*OGDX;q3npA|6f{@VL@wh$E^Rd}T_&I{MbR`{OHvmH
+z=V_J#lZ7m>L8yd*MpB1_Y7Lf%4ugJ18j=!C6a-v(QkBA|_5}RsjoR8jWyVfb`AZy0
+zS%P)Y1f+a2N&;X8(@SaK!ksn}ReRWpfj6LuN;y1tcQd|lW`LNbQy8E5gqSpAT8ILt
+z^bMbQZK8MOzRohcn!R&L!Do&p%2`}D&jpr2-A9OTz=FJCODIVLiJqApxJEKgtq|qZ
+z+&C}CBynugXStTfE#J0)apa3HZ8XCK*$W$gFBTEBk~|mxtDG!bAh<O*hAs%p_t&T;
+zi!rAkSTRe=^2u@IcWT@xexJDC;mZUX;CZ3RWRQtT3hNXnvXOleUkuw4E}2ccK4fl?
+zqt@_&)^NG(Rcvi7BeHQ{mTr*|(#gU2X@ejuh8IO{ke*<q1<y(@1pb*HlrAy)luIly
+zGEXiG8+>2cNp<<(EeJYIBaJM6H%89ynE(H8LGV8}hM|*{?SGsBTq#V+4iX@AzfiVb
+zEi?1}?u<xnp4O2l!d2OoFchrZ>i5TaG<Axo6TET2W1rwZ4@QZM)0Cv-_8i=duyR@p
+z{+uX>w#H7m9A~r>=A_s_uvoz1x(kv<qg$p+xv_SiGB{A`pjg7``cv^fLC2Mc(iV*H
+z4xV@!sE6E3f53h&G?%f5&EJHQ5k06%A&V$x|GF>sUih09__7S`K92;~MPrvw!G|nF
+zp!}6?PM^>?4Mmb7E9{N8O9{-cUtm89PI^<7sQ2B7&zed|x*3)S@M=l$swVNRjiach
+zL+jRvCVf6&G)fjlSdxT{2ot5$tkqND2}L8v-{bGr388*HBN@VdKj`M;#q-^hH84OS
+zqV+dGdng<31fQ@YFYSzF)$T>FfF=+ktvVENsu^hNIhGGV)tbh#(JCzJQeI|Y39=F+
+z8su@1t4`avI6VJCY;)GX8eB)KL!)jdwEq&T%u?hkW0fwiLOh56-&gQmd9^bY>$hE6
+z_J49_{hupnWM$^)_@9@M$IN9zG=AradVQISfL2n-%!M_2xn8WUth3X^qciz|u_GQ2
+zI4(961cVvHR3pLA=R^4^h_@W@uS(|NYcBAXmX?-_3cjj_Nr9WX(wILf{+|R1rTRm!
+z_<@>znl{M?Xyqv+XKh2ZJHVd~)xih1&Q%Q!6Qd^aeU6Qd4HL1O?Pxe{E8H$F*N<qK
+zh%tLO0=%-7f|WnH?e0_9ov$AcH`-C!Jqa@R`ss@|!d~Cez&_r$_q((oK}lnLDH6OA
+zyEAT{Zjh%0Xy>FgobNX`qBS`&Ftst~DL~bh;}<=gt`El>D?6j-Jlmw-r+o7wtEN`m
+z(G8Z|#vhmL@rU7H(qA0vG~ptS?aj{J6vs4uKdPKPG-#+FIRPqv-0EnM3zLFkmY2!Y
+zG?OO-7ZzmRJuG0drAqUt@MQLBv~dpYoRx}=YS=#z*~u<s5*%3ZO5D_=M!Fcsrok%@
+zm%wS#bbfJgRvlxd^ddLLUD!orVw;hZ*8|3g1y6#VOrxPg^0#o)zd(o$hMzM=@}vq&
+z#_BXP=PAO}aEkho@I>vjw;<=?e3ydJ+=+KP&T)7MC>K3fRM)$?#UJ73x{+Ef^xN_>
+zH14OO%NpfrA`DxT?%2Y!qEm6j>YT10=mi4-H`?9UJzTHGpT}3Hw;Mi@(YaqUSg(xk
+z2z5N)7P_64`w?9guGv=T5fOdYwyhoH7G>r=)u2N$*r9+KgKlvpz3X2qpb+>{50s^T
+zpC>|`RmS@^f||**aF6oKBG1t?0H<=FL0DM47niuJlxFecYIT#vmS{b4t#9e%OO>g~
+zmm%;GjgsEFcDzO@B5K7ZYp5T4+#1OZ_|3R+D6!9^;td7za1?o<es*;Yfv7hEM+z87
+zTpjf^dBBYFSI?OGHhzg@?6hhyJj1BFIC91mYxwi#WZFPI5f1X|_2s^MM+-2QO8?Rs
+z`>*cp|IwmLoJEDPff74QqU@@2q;JAT4z=#7be2jh=KMj2ZrtBYw38$X8uHN%O9|sL
+z*Sr6e6+zBH8yyL|6c(4%EE1V!G>tujW8tAgANy!jO|GxzGJZ=w)k#Ev!13P=+s0Ad
+zG=yM&UYjM7j8hpCt9cNSS3C%#2p|iwQ|3IDaH~01pEqjq1*!oeJ199EuQ1W)j6lBw
+zF{GufaQsIE8kecd?pve@RbrWceu)XYmV69VDjQC-)*K<=st^nDtOq8ktUa7?h^a=H
+zQYlz`UOkx}`6NB;@ORg$W%h_S0Do7zpsVy}60heGSm@)ba5VX0(?^FG(&z?lA`l7g
+zQY#tj)r$|<0cbqjaY0}T2qVpZeD9V&pxzNkXsHMQ?ovmIy}GJZ`rZymONLSz7cIg~
+zQ^}N}SOT>qjaqxXTF~N)?TDrjb?I90+eU>4n2T{)vaSMp!C~xAwFlQ_*!v6Q2Ievn
+zk}vT$f&0EQJ{31!I!{>3YTWBZ8TiATb*UU^+z*+1Wf!=jx+uAu;j?9;{|sNSJ<Ode
+z%eMufPiAm?GylqusxfUhzlKw`_v0mF>uMbp(dAr*h&|>Xe$jnTM&Pv*GWu>?U(IoK
+z12w7sybFGZd}2f`AWuZ?`{?m=DhHYNYW}A_Y}H-{#?)d`)u=#lFt3HmPMPt-e`vUw
+z2oRq{JR12q=YBC6hLE~}Rn5NqV?b6$YdUjPYo)nK34l;80@n0YILoZ5nzNJHrTG@V
+zYy2r1iuu;r01PKXM3@oEUA%;Z<8x`Ve<CKfYj0MLC|*K=b~LIRZ2DNy9Bz~C(h?CQ
+z1ZoiimN>*P^-jjXAz*ox&8O{>dq0F%UFj(|SOhu+*rkDvW`WUIE(Pe2AXx1;LXix3
+z@v_xfL5U17Xm1r}U3;~~E!+YLM@IFc=HzAE!fs?`YDkC$@Fmzf7*Ds9XKMm&v@!2{
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..81cda2691
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From b0b4fc4a80588075668710b70069f06fe29687ea Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..9d03b3012
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From e7d34e20b78f73bc4637c8d6072215ab1c151adc Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..b44fd44e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From 550d9172d9983e5713aac5f1e6e2bcea999efccc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..0db4009aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From 7596c9af3aa5fe6c0e267b2635740b5620419b0a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..054ac2c31
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From b59ad113e63d01f909da3ec49c45359c869b37f4 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..db678b426
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From 9769cb6709641f6abecfa5e8eefd1dbc23233e99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..1563073c8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From fbc732ed0ff607b8eee120fa6d8920895a8693e0 Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..8fee3c88e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From faa28721223f5eb8165abb6b88591c1ed3a33fa6 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..0a10bd428
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 445eacafe504815bc0bc5547e3b5e60edf6b38e4 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..00102baa8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From cd0f11686e470d6e107e02c5ea3cebf52a53ce0b Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..6b9dc9fc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 271f95a3ff8b7843ed1034eb3115b26935ae9696 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..e53b85d80
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From 0d708a5a38119d8f8eb3591afb411adffce8d9ed Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..7b1efab3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From d44214189fc3733c18babc4318f75685ae520d5e Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..3cfaa1ae6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 414587aa9ac34997c7feb776f914dd23aae0d06a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..ab6b5785b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 4e5375d28e3ada71716ca6fe507d821109d832e0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..337777979
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From 8e562a4207f808d2c5f46933af65c8fe31ce2e8d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..d2af40775
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From fa52c6340990fee97fbcdf852e168e0f3e281821 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..b8d1df047
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From c66ebac1763768297dec3de96b51b05970f316a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..d8ae4f889
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From d441b4207e4b862d935687f04046b5c0f5d090c7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..1ebd5aa7e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From bf1c3c855c5d05a83fcd28e14a33c2ab4c720872 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..bb05372c4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 12e4e238ab291657f7e2b7d37b000a9861deafa7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..2da24e623
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From 849a9a1fa613ee95a929df3a0abee3af73c65886 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..0a257436f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 2182e595d3df2cf4a796de2eee8564cdb2f281fe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..1f3b3110f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From ef62666077f0ebb68de71302f45b533fbaa20fab Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..8f985bdb7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From fed26a001a88ed629efccb5ff6852a588e5aa638 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..67ab4a2cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From 3add45814f3a2125493658744ffa28cb858398f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..8767167e7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 3176e1707267df1c5c4424c3c7a591d66444c976 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..5313a1b24
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From ee670f38d6043cf94916fe895f29738d6526fb40 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..bb7edc84b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 225ae3aa113584d26579ce0b3537a8fec6ea870a Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..d73b69d56
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From 1a03b1d261561b39a56bd81b1fd0a837b8a2fff0 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..b2bdc9458
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 79da3c7a051e2c0343f5c7affeb9047b4b5d65df Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..2f30f1232
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From fd9c98299a65d09daa03889904304e1516b7d7e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..1be733715
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From 7e4855bc1a51daa16d997898f34032b062bf97f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #2: 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch --]
[-- Type: text/x-diff, Size: 1246020 bytes --]
From b48d3d14db1a4f0c74c6532e4d272b6cb2e60e15 Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Fri, 4 Aug 2023 21:24:21 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..b55b16c81
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From e1f9e76db224f08d20fe57d2a259c430dc39670f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..1d9ddc6ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 2580877e6c3e4132b9fe85cfd117596ecbe9a2f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..9521cb092
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 476ca20260449510e9c15b3d8b8e9e1d30f9c0e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..b18d50229
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From e7c24c3c9ad789c1768b9d59a28e7ffc9efb2d0e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..1b4346685
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From 82ba73765ce544dd3fa19834222a01498bc6fe6e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..ad31d8a6e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From e7ca5f9e63c0f35aa9ee17fb4d2b8e0df0912bcf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..2e7b8c43b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From a1881fa5e4d9fe2493b9ad58c857800dec598bd0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..8179879aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From ff23dadb32389518972f7776d8e7ea3fe9102256 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..739db0fc3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From 36db6264555c1b4a9e707333cf2a85efbb27fdd7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..d0dd92255
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From f204232644fdca841292f618a78c5605cc5b9ace Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..bd8015190
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From f59928a5ef7757b9a6394d33005973c0bfa8b6e2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..be8fed6b0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From e2666db837cab4424984c445483fc6fa30dc0acb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..1f038d390
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 07f7fb53b8adedbef73fe8d155fafccb59b5df99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..24687bc88
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From a4198508c5701f51324278b74b5f75c53cd5bb84 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..a1b073ccb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 1990a3630cb6b5fbea3aea69131445642105551c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..5cf188b0a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From c7ac4023212c0193939d3046c988d58a2a13fc79 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..f081624e9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From debf8dcf55e7bd1ad6d18c832acc322431497147 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..982bbf649
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 144131735fe58c02fbe8ff51f9715c230119c79d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..c7ec34bc6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From e579e0758c1350f4d236987c592be94107c2c8a2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..4a907546a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 6f424ed40c509e007bf7cb8d56e6586edf558e7a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..44e37cb57
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From 94d152d86ad42378a90dfc10d614f95d9a4eefb7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..a2b3e1e80
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From c5c8623bafa8733cfcd227922e93713cf317ac22 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..9fd98b07b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 73330bab6864704929d9aeef41e8b8d3d59d5738 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..bc1a21316
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From cc023600296c2b6fb8711e4878d20ec69534d477 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..a9ca3adea
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From e698249e72c33d2964eaadf39ef4a272b54d74d5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..3f73d0e64
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 00aa14dce6b67e815aa6270506ff5d3ff2d54b4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..aa5f5f565
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From a06e4ca5d41760d2466dbd521a12057caefbca8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..ff9c66883
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From 7eab49cf957e0acb374cf847b2a29d4ea6f4a984 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..2960e7b65
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From a43143900dbc60bddad125e0cab3e2a3cbdb6445 Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..185b4ad3e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 7cf03e3379b6ad313378ae1a0f02db91ac4ef177 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..b466102f1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From 3118b73a33edbe1795eca1f4280689448ecb69bc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..b573c613c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From e637e18d1d53bf9b725d64504ef48a475e9f4b98 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..a78f24ad4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 58c9abaaecb194d267c76a91dff7ab2082b4791f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..e63095dfc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 7d6f883c869187e24f5a753233c6c9de4f828302 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..a63a843e8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 849d88f3fe45377086a5895a3cc6734bd9e52e3e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..de765001c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From b02c3ee67e31d6660d252bf59dd0b3693f0c2a0d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..2befaa0bb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 0ff2af237c9cfeb8ded9013451bbdc098378e649 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..0509ace6b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From 2e532b3e0a0bdc69201cf482eaea801f63bcf626 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..6083312a7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 6db1f8a0fe160e2601cc900982ed5298fb78d3ff Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..dcffc905e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 068db3313b7db26b53c652033af2f1f2bc5ced65 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..a5a60097e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From dd6b79aa7160620a2a195339df3f293ea5a71bf5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..32ddd96f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From 1e810d11afcfb1050f44be983cf0caca505238e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..1c519a0b1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From b42bbef5f45ee3e80947f772592b31d8bbb6528c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..a39679e7f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From 7eee31ec573b2960c9a3013c0ee70c2795338a77 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..191375c2d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From 7136b955fa8fdcfd2e66fc10d8875fabf5034d3d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..7b07e4a18
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 80632f2f48c58a69673991991041f7192f70898b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..0aa893110
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From e57f08d43500ef11200f230ec6c0a51f75183fa9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..07439e0bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 9583c2fa90e958caff51b3b7ca8c176468123fd9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..a8e65a214
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From a3069c1e9885c4284427d5d96039cd7592a27548 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..506a3ac60
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From bb97fca990e33d280f9ced9da31e2274a95b6766 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..f1a6b39fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From fcc1ba9e56f0dd4b0d2ea72395c5cc5ebb4abd01 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..cb8ff99eb
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From 08e2028d427198289d7c94e14e84a91034bc5437 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..48e3e6985
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 5b42321cfe95d29e2d55ec8f2495be07b4173ca0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..6bc55808a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 788108509add4a74c439636dc80f2c08ab5e73f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..cae9289ea
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 50f81852adf0efa2490dbaa95cde8732298a1a83 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..1bd25ffda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From d176622dde4b7f58c892a01cb9338177e938366b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..ef05f337f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 80e1438de5bb34b7f6afa8524b85fea3f14389c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..3a9de09f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From 44a0c078becf1c20383f6f4cb2563dd9cabd0108 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..8857b1ca3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 200892321d280e590428c46b524c2c2f22af0baf Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..d34ac05de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From a5e5226f44395d4e889873c5e39a20322166e2c9 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..76549dbf8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From fda4886fef5baf28a7b00aa6b9f1ddb94ce63fc3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..699433e7b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 70ca37d397aac99a09af4d4d32ea0aebae2815d3 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..568c78722
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 5dfb9b1c5e97b5e1ee510f8dfeefe0dc859b34f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..5a4875c3b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From b976bfdcfba7bfdc7f5df7694b63a9b7bf683a4c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..a20e7d8ae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 77159cc3be1cef3502199c8accce7479cfee3c3b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..c22a74b61
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From 34d5215c26e6adb74c94f53b5b4343ea8d20c411 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..842798387
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From 0a14bb8f51202ce1031211e64841927e8418eaac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..ef510000a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From fc4da9aa8772d5a89cb0dd3702a53722d1499ade Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..2af166929
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From e636118154b10526e075c3783a439d58c1f0e0ac Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..4691d2dde
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From 8e30bf419609719ee1904b8efa8ab124fd8b86b2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..424f0e046
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From 9c1c8a528fb73700074e09c29dfea9045c5e88ee Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..8e2187045
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From 1dcc6043aef5764b6057b466f23b90bac00d6dbe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..fe140e9cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 7f36dd8a9a1d6fd67f8a6765cd56fcab9b10207f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..230de5106
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 2e3bd7377116e63243095f977c816b8624622992 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..57f947927
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From c3279d3990d3391972e906c18d6881c6de464685 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..1785a98b7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 70d9831b52d0db1f50bd313703d02708c8dd63c6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..a8562a5de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From 8fab3e25509d703f0e5bfa99c2ef7f0648d144e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..0479baf96
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From f785f20fb8d5087a7053f6dc667bd28180a222d5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..d6a628334
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From 51cd0b705d9a3cdb90e9b425791da3f4ad47d6a4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..b6803f007
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From 4558fe4c9c728d3f7acbc2cfd0912f05afd22a2d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..3ca2089dc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4b9075996e62c4e57eed93041879d74033c82579 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..79f0c74e3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From f39ac70787255ce3a0e2567535c4b90f0e1c8e27 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..922357eda
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From c06d8a3ab2d195c933c031d8d25bfee343eb6ba2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..5eda962df
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From 5cef8e80746eb530235456ca4465e90d12c47aed Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..573e7bdc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From ef224b88d487a1c950986f0cf492c751efb96fa4 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..5e94bf8fe
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From b5ac196d4d5916aaa0d2c1cbbf03d08c6a32a7b0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..43825706e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 8bc30cc7e357839ebe02110c7e8d2000fb92384e Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..1f5f1e6f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From 6180cb6894a8a376ae86e8b9634dc3e34985e69f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..9a013b2b1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From af346a832f1e03f30daea3533a885d474dd9f4ca Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..b95a13373
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From 92d59fa871192b6b6413113d85a0fa0f924c00d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..81cda2691
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From b0b4fc4a80588075668710b70069f06fe29687ea Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..9d03b3012
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From e7d34e20b78f73bc4637c8d6072215ab1c151adc Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..b44fd44e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From 550d9172d9983e5713aac5f1e6e2bcea999efccc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..0db4009aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From 7596c9af3aa5fe6c0e267b2635740b5620419b0a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..054ac2c31
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From b59ad113e63d01f909da3ec49c45359c869b37f4 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..db678b426
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From 9769cb6709641f6abecfa5e8eefd1dbc23233e99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..1563073c8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From fbc732ed0ff607b8eee120fa6d8920895a8693e0 Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..8fee3c88e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From faa28721223f5eb8165abb6b88591c1ed3a33fa6 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..0a10bd428
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 445eacafe504815bc0bc5547e3b5e60edf6b38e4 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..00102baa8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From cd0f11686e470d6e107e02c5ea3cebf52a53ce0b Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..6b9dc9fc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 271f95a3ff8b7843ed1034eb3115b26935ae9696 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..e53b85d80
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From 0d708a5a38119d8f8eb3591afb411adffce8d9ed Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..7b1efab3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From d44214189fc3733c18babc4318f75685ae520d5e Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..3cfaa1ae6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 414587aa9ac34997c7feb776f914dd23aae0d06a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..ab6b5785b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From 4e5375d28e3ada71716ca6fe507d821109d832e0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..337777979
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From 8e562a4207f808d2c5f46933af65c8fe31ce2e8d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..d2af40775
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From fa52c6340990fee97fbcdf852e168e0f3e281821 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..b8d1df047
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From c66ebac1763768297dec3de96b51b05970f316a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..d8ae4f889
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From d441b4207e4b862d935687f04046b5c0f5d090c7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..1ebd5aa7e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From bf1c3c855c5d05a83fcd28e14a33c2ab4c720872 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..bb05372c4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 12e4e238ab291657f7e2b7d37b000a9861deafa7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..2da24e623
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From 849a9a1fa613ee95a929df3a0abee3af73c65886 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..0a257436f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 2182e595d3df2cf4a796de2eee8564cdb2f281fe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..1f3b3110f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From ef62666077f0ebb68de71302f45b533fbaa20fab Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..8f985bdb7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From fed26a001a88ed629efccb5ff6852a588e5aa638 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..67ab4a2cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From 3add45814f3a2125493658744ffa28cb858398f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..8767167e7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 3176e1707267df1c5c4424c3c7a591d66444c976 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..5313a1b24
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From ee670f38d6043cf94916fe895f29738d6526fb40 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..bb7edc84b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 225ae3aa113584d26579ce0b3537a8fec6ea870a Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..d73b69d56
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From 1a03b1d261561b39a56bd81b1fd0a837b8a2fff0 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..b2bdc9458
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From 79da3c7a051e2c0343f5c7affeb9047b4b5d65df Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..2f30f1232
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From fd9c98299a65d09daa03889904304e1516b7d7e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..1be733715
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From 7e4855bc1a51daa16d997898f34032b062bf97f0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.25.1
[-- Attachment #3: bitbake-output-qemux86-64.txt --]
[-- Type: text/plain, Size: 6338 bytes --]
Loading cache...done.
Loaded 12633 entries from dependency cache.
Parsing recipes...done.
Parsing of 7341 .bb files complete (7338 cached, 3 parsed). 12618 targets, 325 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
NOTE: Resolving any missing task queue dependencies
Build Configuration (mc:default):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:058a44165ce375f405063e73a9fcd1b2757ef8da"
meta-oe
meta-python
meta-perl = "heads/origin/master-next:29edbe45640096081b9b50755d33107dc9237953"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemux86-musl):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux-musl"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:058a44165ce375f405063e73a9fcd1b2757ef8da"
meta-oe
meta-python
meta-perl = "heads/origin/master-next:29edbe45640096081b9b50755d33107dc9237953"
workspace = "<unknown>:<unknown>"
Build Configuration (mc:qemuarm64):
BB_VERSION = "2.4.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.2"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp = "master:058a44165ce375f405063e73a9fcd1b2757ef8da"
meta-oe
meta-python
meta-perl = "heads/origin/master-next:29edbe45640096081b9b50755d33107dc9237953"
workspace = "<unknown>:<unknown>"
Initialising tasks...done.
Checking sstate mirror object availability...done.
Sstate summary: Wanted 376 Local 186 Mirrors 181 Missed 9 Current 259 (97% match, 98% complete)
NOTE: Executing Tasks
NOTE: Running setscene task 269 of 635 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_recipe_qa_setscene)
NOTE: recipe python3-behave-6-r0: task do_recipe_qa_setscene: Started
NOTE: recipe python3-behave-6-r0: task do_recipe_qa_setscene: Succeeded
NOTE: Running task 1339 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_fetch)
NOTE: recipe python3-behave-6-r0: task do_fetch: Started
NOTE: recipe python3-behave-6-r0: task do_fetch: Succeeded
NOTE: Running task 1790 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_unpack)
NOTE: Running task 1791 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_prepare_recipe_sysroot)
NOTE: recipe python3-behave-6-r0: task do_unpack: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Succeeded
NOTE: recipe python3-behave-6-r0: task do_unpack: Succeeded
NOTE: Running task 1792 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch)
NOTE: Running task 1793 of 1806 (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_collect_spdx_deps)
NOTE: recipe python3-behave-6-r0: task do_patch: Started
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Started
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Succeeded
NOTE: recipe python3-behave-6-r0: task do_patch: Failed
NOTE: Tasks Summary: Attempted 1793 tasks of which 1788 didn't need to be rerun and 1 failed.
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 3 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 6 seconds
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 4 seconds
Summary: 1 task failed:
/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch
Summary: There was 1 ERROR message, returning a non-zero exit code.
ERROR: python3-behave-6-r0 do_patch: Applying patch '0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch' on target directory '/scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/git'
CmdError('quilt --quiltrc /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/recipe-sysroot-native/etc/quiltrc push', 0, "stdout: Applying patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
can't find file to patch at input line 15
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|From e1f9e76db224f08d20fe57d2a259c430dc39670f Mon Sep 17 00:00:00 2001
|From: jenisys <jenisys@users.noreply.github.com>
|Date: Mon, 11 Mar 2019 22:37:04 +0100
|Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
| description to created Scenario.
|
|---
| behave/model.py | 1 +
| 1 file changed, 1 insertion(+)
|
|diff --git a/behave/model.py b/behave/model.py
|index 4ad4b9d..9dd68fd 100644
|--- a/behave/model.py
|+++ b/behave/model.py
--------------------------
No file to patch. Skipping patch.
1 out of 1 hunk ignored
Patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch does not apply (enforce with -f)
stderr: ")
ERROR: Logfile of failure stored in: /scratch1/auh/build-auh/tmp/work/core2-64-poky-linux/python3-behave/6-r0/temp/log.do_patch.2909069
ERROR: Task (/home/auh/Projects/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch) failed with exit code '1'
^ permalink raw reply related [flat|nested] 4+ messages in thread* [AUH] python3-behave: upgrading to 6 FAILED
@ 2023-11-04 1:38 auh
0 siblings, 0 replies; 4+ messages in thread
From: auh @ 2023-11-04 1:38 UTC (permalink / raw)
To: Forced, maintainer, for, AUH; +Cc: openembedded-devel
[-- Attachment #1: Type: text/plain, Size: 1247012 bytes --]
Hello,
this email is a notification from the Auto Upgrade Helper
that the automatic attempt to upgrade the recipe *python3-behave* to *6* has Failed(do_compile).
Detailed error information:
do_compile failed
Next steps:
- apply the patch: git am 0001-python3-behave-upgrade-1.2.6-6.patch
- check the changes to upstream patches and summarize them in the commit message,
- compile an image that contains the package
- perform some basic sanity tests
- amend the patch and sign it off: git commit -s --reset-author --amend
- send it to the appropriate mailing list
Alternatively, if you believe the recipe should not be upgraded at this time,
you can fill RECIPE_NO_UPDATE_REASON in respective recipe file so that
automatic upgrades would no longer be attempted.
Please review the attached files for further information and build/update failures.
Any problem please file a bug at https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Automated%20Update%20Handler
Regards,
The Upgrade Helper
-- >8 --
From d9ceb27176c0404fd6c577dbebd3ad671b58af1c Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@yoctoproject.org>
Date: Fri, 3 Nov 2023 20:10:50 +0000
Subject: [PATCH] python3-behave: upgrade 1.2.6 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..e94b2f25c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From 0ae5e867f73ba50744531024e3bad79979f1dbaa Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..8a113a20f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 34a1d26a1683d4d952da7f32c02316d67cb13925 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..e0f34b532
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 92baa7df57b5db861a03c80034a52e6e7681c8d2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..6ed826b97
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From a0678cdece593697082973abbb59648f27530c99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..6c5915c4d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From bd74dcc8af6b48d15a1f2e8158aa53565a4bb45c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..8fa8b59fa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From 1cc411a82767fd63b4eff5bc9cff6d7867d45d17 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..ffd433857
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From 466337566f163bc7339fe20d12d8fb8561eabbdf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..c0e9a67bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 6b698d73616c3eefbf67c0bb184755af5334f37f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..a3d12df88
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From c27f43ef8270c7e08a20fbb3066c8780f26ecccd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..71bcb8d37
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From 9b5204ebaa619f2776f2c6350c619927327e5aca Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..94e7d6352
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From f7313c14ee5b1038447d104a885bd0f99875b3e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..b8f2527a0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From 1c8b368ed9016f9db7f17851144250712717f1c5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..dcf234771
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 60438ebea003efae558ea344fbeb633cf21a8267 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..5eedb5875
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 7a7984717e1d5278bac29283e0870f0a638b22fe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..584931bd9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 603bae920105bb97b4dfe2979d08cfea61e72dd8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..dec47bbbf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From 4a27977f21a0179e8d0c01581e93ee689f0b992e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..d1dd355ab
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 2bdb1cd02f10b307e11df7f33a410401ae8a49b1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..83c01dc49
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 4c62d7eaf7efd930b6d4d681c5ebe0cd901e529e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..d5535afe9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From 89da35f7b165d74cce802aff5ba9b4833bb2171d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..b72886009
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 756f9f7e6254e576315880cee3feb32e9dc7c452 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..5cd5ca8de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From 34e0fc3657577c00931663501087788188b5bfdf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..462fbb6d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From ebdd9ce8253ec22f1e31dbb5cc42702c949af301 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..3c6eda380
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 67eb237284f89d694bb09ebf7a3166c6c08cb025 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..f39386642
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From 958e8ddce6dd80b211c336e7b809b10e92ef1fa5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..7082dbc25
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 5a039c49d0e158013bf308e824212decd3085fbf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..d8f68c3bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 5619b3375f9a4bbf760dafd6e77945658d5e06e6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..b4f54a2d2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From 2fdad16ca8bbca127b6b181da2ccf4eebb58a8a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..d40c78afa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From 2d151afe0191a8c227cb873e577575eb9d78057f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..f85f73e83
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From 18784841314072f089f47ef208851ae475271acd Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..3839c8d55
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 1b9fa431aa92cfee62c1efcfd74f72774707e6b2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..01b30c7ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From 17fd92cd7fd764757651097e9bd947ce35e39196 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..a9e619c58
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From f6f22d97662e74fd728648f6d42a7f07bfc60dee Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..4f520396e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 18bf6e97784b4d1b4c91833826af8963e0daf612 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..f99a1c226
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 029ed12454b56682849ccd0cec00c9cb4611e23e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..714e3a530
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 878af8e5a05b138ecbb63bcfcae522669cabb034 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..3b0f4823a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From e706a7720cc8e75b4fd8e43e43139901a701b16b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..8021f2088
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 6b9f9eb25283c2bbdc742aa2f3fcd5bcb33ea618 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..d64c466f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From 97f18f67e7ede362b5817700521df790479d8061 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..9041195d5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 9b451c67a09acafa4c82af278535368cb1ee9b4a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..44611cc84
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 6fcb1210f8d2790c41475e2eac6de70095da7865 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..94aaee98d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 89181e0c0edef7b723f4d314c5bb55b32368df49 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..60cc8cc4c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From a904d8961c44cb101fe29b367f979453e8809c8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..300408616
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From e330aacc06169664183eb05a5fffcfd902d602ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..b1059c4ff
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From a0e549bc9595c23f7eff7202f618695bf7a548a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..568ea5407
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From eb55a7ba0f503f22dc37312db544e2307c23f123 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..8f466fd60
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 5bdac70928fffefb2c17eb499af00e6c5216a38e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..3a2ca3100
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From 85baf33a50a824c7006a7981cb80871728a47265 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..c5fb4110d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 539683fff83ddf41d0b342b4c6c906c64f424fb9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..65a4691c6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From 32a7500d834b1856ad5f7f3af4a19acaef61294d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..1d55043e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From 8ef1ca5016cfd4e417fc8b6e8becf1b1d1ff0767 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..0307246cf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 23a80a765e10dad0d363631066c17bd1131e8015 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..bbdce1e43
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From b6468eb1b71bd0877936882f57242b0cfa50c6ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..a5e627c9b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 8f6bd98e566052d79b76f604ee792ffe456e859a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..e314d9118
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 393d90b6127b61def5d9d12444c2a747a03f7ec1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..926fc7233
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 1983740f7df60c4f1795e81c9cc9b4c33200a594 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..82903091c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From 86093166f066e74b61a9bc10ab3e6ea2485f06eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..9d5dd1978
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 1e3acce3b4d478400253656db354974326f9fb8d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..af553077f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From a9b7d671cfb06d49a16fc1f559b6e7e24b04ec13 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..1a0c1d6f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 26e15e7a1bb5b33db38eb0c77730162dbb6a05d8 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..be817883d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From 70e538f77e88bef881b33fd50ea00e9a309acdc4 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..ac31b2d41
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 8b81a404d50bfa4a05ec93c0a8220927a5593d6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..874c95b69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 5d4478bc85f3a12efe0c85638665e23a0ab233d1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..6f5a1dad9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 507ce8af0c2513893008904db3a7dbd9de5ac2d4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..867e16b22
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 0ecb55eed2f0d4b7aa49cfd50f9d8ab34d70628a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..93e70d3d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 5b60bba6b115758155c97eb8aea22c28c407fd6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..f492b8a93
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From a7a1b60b3a723af51515ba64170ae99695755df2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..9f1a17fb3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From ab69c9eacffd387dd756425030d287cd182df12f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..702b73023
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 3883099e542901f457835900d42a60ea671713ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..098f6ae99
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From 1f318bee1273d6fa245eee348c72bdebfe28a703 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..d0e9cb005
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From f18ada8b98ff62c47d51bc38e5654e7c363d34f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..1709b9abc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From a2a8db7c0a5c9e7a6a43abe9b7c5d9474c13e75d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..2ea9fa8e9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From bceaaac964834add6016c6e7fb62ac512da99072 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..fc2b8eae4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 7c8f5778ae1f325861d4cdbd410c553829ca08ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..588ef50cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 041322c92c07199822fe4faf488b3afc6bec30da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..e53b10c92
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From b04042837ff57000709adc716f711b29ccc59584 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..b92043f6f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 18ff55874640f54652d13874bec5b67f6be12bef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..689138b42
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From 878f1b58cb0ef3081279cb1cb5673250b3665d6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..711233ff4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 8e7d3ade79d4b55d0704ba25dc4d0082b64a1315 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..b20903214
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From cb1b3d768367a129b944588b8ddf2971c3da26b9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..1cd96abcc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From f17fac59d4c0f162193baa4bedc3e8df8d11eb34 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..5fe3f774a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4b27f39342afa14f1554458af808958350b8aeda Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..928fb4511
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From 3c38942c26f91cb5c3047b8c91ba2e8e5a1511a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..d2b36d5f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From fa3987c3504d4857b3b0cb3e0e1767a69bdadd04 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..1a3d2ebba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From a3643426f04c590cdcd48be61237ee2ff723f551 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..ff24a58f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From 255ed96600a80fc4be0fda93b3db790112b08cea Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..b49257583
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From 65b9ec6a6a36df4353e36acd512ffbb3d2b39679 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..7882ebd21
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 4424234ade658ab2d943b77c48dfd9a6e63b2c05 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..b821f923e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From ba70dd5d6c97b2732600adcb4764206b936a0a36 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..67d1c4b53
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From f9a1c7478df6b8986267b68cb09ba62b635b57e6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..1ced592e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From a84f76c5fe60f3f3780700ba69e6eeb5dda73521 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+zlijISVVH3?t_u6vnZ7iNH)T=VP=tVQWFIGk92;+nk}&m4DO*F_k~K&JQtGiCZz@sE
+zLUi<GwJ#v`9S%+-C`l;|Fi`a(=e2OhQ%&CX6R7I$WhV0<2N29+O{zpXML<GdIEPq*
+ztSOEoz&K}^M$$l^47e_Ep4s+Jv2t>dcC>c-ilDcSXGS~j`aNEs>cXS~lAE}%MaqPM
+z0F?-k1S8e++aEqPX1kmDE>-u2dcrU3ig@YRJV&1K>mnD%gPIXbKx<#Kh)fnGG>0#n
+zSJC)Il<@xEmQdP}SIjE1Rke);)sPwaCuP#Cyynb4meAU&&QjxBKCLO9N3y{@9z%t2
+z?}2M*I#INor>ol>VqToZEFhM$w8Zybxek1o_>kWtaJ>Z+9;Y(@Bjf`Elfqo`IR-$z
+zE1pU4dHjKN;z<%Uu$GuZ4-|fD7B(cdhp8+QhZz$Ful;Z4*WC6YP>byckl-@VMKgp|
+z6c9ofOQu-_YOxq(!OJxyGFzP{(_Tp<qlxrc5ib12ChOis9Lid-kxZ^0*nd`=$GJI$
+z8NQQ86EHMYG6`}@%F9J&jnM%@>ctv1i5}Z4nAPN8O)_m_<0D-P+71nFd;?ud0}Hd$
+zI_98w$#e&@m24NXe6+`lO{6UhZYF|7som8$No{&XCi2j!4!+a<7r-0?k*|@G)jLC5
+z5?o4rgxa#B3>tJBa1zaFqk$i_m2!2GN~#IxSP*3Iu`d`06_udE9*T8!fiH=B0^eEb
+z)*NolWq6t|?!dqB$Cn)Jptf3$g%cAX;-fjeAJlZ<;e*7d2>CKU8da}~`ZR<yl@omB
+zvA;qQjB=DSBG^pL0QnI0hDl~SoJs8G=^YIOo7vv7Txa<E`|FL>3}gHqqmdr2j-6bI
+ze66)=f``h<2$23#e9y$?B06+b=?3_oQJ&#^B9AM!N+CZ9mwi)+6e+fCB?@%1R%{wJ
+zp7R`|%{zNO!ayj^h1Iz+n|Xdm@(+${xk^8IJ{aeq$gMi`WJNb~GLLzQuE#^KIXKG5
+z?5f_VLp`YMe-DjQO(s9EFGyK9T7t`5x$j*CF7j)pE<G08hfhn5eJ<IE^Wj=2p}?_r
+z#AF3?E6dRMdbk<V3J1U8lq?`E7TYd4dLKYc+`_^1aQ%YD>{b9@@Q?sf@L*^OKfV-+
+zhV0dig~_;V;y#@ldMWdQl<;p1LQ%5lC|Mb$5op1(JmrGvSHHs2+JC`7K-GG86iN5J
+zMk?EN!)0hE-8>^|{d`64W#6aFNl@*V8$lkov06)g-ES`)!mIt&{p!teQ&00aRb*WT
+zVRvBGb<0d^<Np&aX3C2nu?%H_l&JX+f<uUA7vmicvtkPt#sWJQ-0@Y5h7xu`tkGiM
+zu)uzLOLA%hjxx}L)}P)3?om29WO+Po9(;=T9Dq>glc|905|qa(ZWV}AzFTk1i%SRY
+zN7Gu)vqO1$HF*?f9ZQS~#1{I?Aas`@!~%yLq?h0|4<L|DVvmj-R@jgy-w|KFKEUOM
+zr$FrorU~6Q%gUYl<p~E>w40d{lBNH<5PJL&3Cu<#E7`&7`W5FbIa(7Zl%@DO8wZW-
+z(yrV82S*W%G*vVZ>v$f%q10D2P!XPb?f8p4h<_3Zo^ZT*Cy_h;Ps5_Ex@f46o^(q#
+z-}q(h(wwK1IS5mT@lO!T*W3X+SIJHe5yZ(+4)mciwQka>Ln>)aA9ICk_FUI-1pkmF
+zXc<y;C>~M3Xipy0{36j1fTLYgVSSe__f7&QJz1=kjWKRT8Wz4fHtu|kv4!|t2}T;E
+z@et59*s}c3umSKa-wZ4E%NE533F@hMcFd}WuG_=Iik#Y>Z%=l$Dj-9JqWGVnLZI}5
+z+|uXQU-!r7tu{KnZ<m(FGja48`pZrE)$L%*0BI+eUOvowPuZR6yrZfMuV18(S=wL~
+z9HUL#o@&cse7|ZFE2pH4rwB)J%_nQTqW!d`MN{94<|?;XPtIh_mC~8dFXYI$UW1Fj
+zf~=W`xe2o!o9OdNDi~v>am!kRbRore=z}VC#*K;}B0CMo2TwbIZ&{^I=4&3Q<z{ty
+zA6eaY^2vbMWb`?i7m^45&T}a8330BU$bix3vVXgO?N1SWZ$~0Jcjkkcx0GuL#yOmX
+zAD55g5wQ#L?-q%=h4)IXyedkO+yf@GW%j}EKkc)py9LlzmtL6rPw8T6yqq=K_dJNT
+zx7s#JouG-)(|yVlZ*NGHVijxPx(WJ~Fp9mJc9nZ>U*`06bF6yBscMPb8~kcp85|uo
+z$<^L2C&`ZET3#$X+zFu}KV->hs;K78{8+dnds=kCUEy0;psJP7@JZZ54;HRIQL}~0
+zMcnMCoEeZUhVz;TY3!{0cmmV8d&vjg*W;JLVFU)S&yHo>B>Oq*_iz5jS&zpd!u;Tc
+zk0OWKL)zg-bEro|^=fn=%>OEKJXwwZShwzsfnHnkxT<fot0-Zkvj|L;jfO>QKaab_
+zX8jS!{lIJS<0a9Tm*s}x2=1#N;%e!ZL-Vfe6Brr2SKBGO1mq^<aR|2xtoWph!Htl0
+zr@Pfy{Mj!<RW9f0&i3)_n=)i{>a(C2bGVDYTvvR!h@yoJy;oKtfi=bld^z^$ub{=R
+z{6XiXE^YYo>#=`=9wADUY7vXbJT>_DCy!)|M=kHP&kscMDKtF$H0&;$1!OV60JC9u
+zx0?MV51iO;&G1Yyt?+){QCdKn>%UQkya@AIFB>cWt+Tw?_?E@u6nucRgm-yn-r3(_
+zKZgdM11mn%QZM!w7ZkR{3-`~ft(nn2J%)l;hKL{PdMLVEYAGosR(JUcBA@~+!$Y~g
+z_r6T?w;x{e_gE5It=#z*c*dT{-nBNc&trw|h9Zo>wNC3!0j{ua;cPxps21(4@D43j
+z8AkWUoFinzy04u6C*hp92&+dojxennmZ67%>P`}W+qMy2NMEWAZWjkxS3(}-_Fc^I
+zr!h8n8Bc}1)$g!$`@e8`>8OlqZ{n5=&#PUoOegHX7Y8;7C5BcJ=?+GcBrk_ZYpTBF
+z^o6DKkOb|AbiIw?vrT-lp+rb%3O03vGsQX6MW``9yyJz9#4{VqZkK=e)DYGX^irLT
+z89GuZo2E|Tg0ep9n8bY)5or-K6ZI@Z-uXHL)q5QEDBFoAppMcbY{~?yL9NrFJE4BI
+zYYNm6m4t$7c>U|#ZoXXx`_dr*XF5Sh2*WkOK_=Xn1Vc>xIN0^oL&=pzvq!$`;YIjw
+z(Cc?nJNdL|5rtj0xJ@5L!^LP7fb@Q&*-Lazux(2<hDqN|z?~OA!uUF-=aov!+KsE{
+zj}X-enqwE5AV!i4r8$l;vd9_Pa<AAuXlk+)M{MyPOB$75nlEz3B&O~kH5BQa5<H;~
+zT#2tPd6O-4$EO?#nh#T!wi5OV{(Swx>)cFp^F5tjGuV-5+vhi_SE9Y}hu7;cjm!D*
+z?<7DUDmJWC+q0xONcJVS<n5N(j*XNvr=sTq>S3ZF<&M^i+ju?$YjaH9sim{4ZVV9w
+zybMk1WAfw<F8yJ>*S7AaKY+Kxw?%E`1ak%oMSbB0zPQ`vn0m&%t<HIOtXXqE0U_0H
+z!6xl}fkuAoehg(SHX2EG<O3>=LrW{TUDL6sd$LGo(^=gk49sOu5fyS0Ex@p=h@u=Y
+zvOrzruHuX`kzedAC+`zJTjCpS1Tr{j+;6?jW5P288grX(PfYUqcbmBg<;XT)VLOq*
+z>3+@1`+a`{QeU$LCdQ-o7u07rdG3^M7kdYebR`^#s~+^CqtDCGis(X*GT8>25T>nh
+z!G7Zkm48R<nIB2rR}08Lhr9GKPHn59(k`*vxONb%xS{RB)4ed27Hc~aKNk%`=KID;
+zX}+@5mTRaQVv|vCDs0#&8Z<9gAJ2epa0e~f_m@ydFE<YdQ#EN>P1KX=|6Bn_?H4kF
+zzr#%M6~XstP6$@3{#dL7`nH|w94R^+34j6Ulirj3Br+-bWCG1|7BwyJgkrJIdi2Hr
+zoD#u!pC`z1Y&h(s$Uq(9wCsPGo?RjID^60ALn05}Ya(@~!CX8Pa-;9v?FnxftIQPi
+zCmpWA?rO$a{SlX<X-7i&B@%Otgwt~@cK$13S?j=byFc8*VUxajgXch$<SUH98m#vg
+zZaNyxS6IR@0C6$ZN&DEkkfx6P>+HKG0WI9=iQ^6$#06?M{-?8QnKjKw2t~%>baPCb
+z^dzP9?aWwewAknBj?FXprm(U@yphs^iTBD1X)*n2YCFz^UW-)wXOQ4ja;(B_%cn>X
+zZh$F0r<NiD?B4yYH@X4qY6=9DfT*v&$;UG3*WUc+MR5Kn!8jjH&*4upeqAfEa60%Z
+zXy(1BY0r^dTsS+BAg&nWi@AuOv9IgK8>0<>VOJ7U_2H3^S__1kg=(JOOIj=6C)-c(
+z*K<t^X}r)xSQiTCiX#{&waoMRMC)74jzO*TOW=-nwq9$Fna{@i<9i)3n>lv`DZ&~-
+z8SYKTWfTD4Ezsk5ug?5iPFYIblvNEWt|uUhbIJ-`aLW(Nqgrbq7Ps3DZO`4J^vzQI
+z!9dGv65RX9-~2TGkkcVHoCQ5zKeUtX9bOUzn2<ocH0T$U=$y<u4)<(?g-os2o~cWR
+z8qriVzE4c{gY7LJlVX?$M?5W=4#WO}_wTbO{ZRz;Nz+@q%O|8~&b0<>fAV$P4vq3x
+zzhN6&uO=&N>yFu-&*8~rc2199?)Zmonj>%N1kqrl);bx@{t~@a97&+V&A0FN1gq>U
+z^t7?JLC`*20#@Phw~C;Wqyl3dE*<nnRk$xxww^FeCOjvSfciaWnT3N+Gi03-1^Wq*
+z3piR}{b2~ak2e%hir$q+iXg0Pl_z;1-|Mls+S?@tuML_brWF*#`W9ON>)i4#sMi7P
+zZJ$Fp2WD@xIQ{7*K#B*=x5PlBCv4F=BJ4YCU;RM_3O#-Igpzi`DBuZzOk)YB0c-M;
+z?6{ZY?#vG@n{Kn`(7%H}^|7CSdjJia8RAc7FEcUcJfSc$Ku3B;CcVi(F5NkYnY7AY
+zn#Ghw@a4fWi8uPduKeaw=$8dyO4-PB=1kIu&};uj!Fz0ipCP(mnO6QK#1~rjy$S2O
+z&$uP~LMZZGQH!O$dBc|!IA`WnU}j!PyaR=KHC>V+Ex+9m%;ovn_>>oMCmdRWy&Lv;
+zTS?H3`{557!a5>yJqe7%Te?~r+FVR<Xc~D%cHh0%Lu*T}goYINh5Pfd{L`{=+|$+&
+z@>$yR?nZvU;KPzJ*DfrM#H)*J?hTiuQyP#+|AWnV&gwS0;m{HgXI6Q2Xu^!Er8CQS
+z#)bY`%-}wwr~S7E_U{{e;#D-@CjDaqrx4#?4A4Pe%;hW7#{2}^GEeS@oDwGz)_0D}
+zeyT}lS?u;hlA;86JGk44J#22rRs+JoqY#nn$3S!_T~`wg54C7#TQEM3^QZ4KJX2JZ
+z_a~d?Ge-|}O<78)w&!(2KiFQyZAu{*EI)8jDa4x;mav3LQFH4_d2rHg*%v8@Q<{1>
+zdjtZBu9Q$McL;ynMrJRKa<Q6&$XnJ?oXN7wyM6@Z$Ex;z9uwg<7wsvv22%CQi6tdO
+z;7?NB98L7w4>L!6FdY^#GX`wQJ1Dcc<1WH$`FPR;MANA-GzaP>TsPQDHR&ZNM}9(A
+zt6DeA1-yw9sXOkhs%r~G<<J>GQr-_kC7vU1S!BeUG<;m<-UaMr=9MbX7!(lfJ%mVD
+z5zoU%#O-1|_kjp0ykzI(6Hdy7Bswl2I@`qtNf5sprPf;3i~Po*qU_&2K>nj+fKqu*
+zd^_zlw?76%dCHku9;ja)3a>r`7ZY&`v>#A{%7b?<G-OQ&nfv&rs?7$JP_3hVFvg@)
+zT3jz!NOLb&)?2AIGybx?`Oiq6fAdVGwYdv%VbL++K)Q0W;xJZw;*`GTeZUv5#~xIY
+zS0<Uyi1q;99*~RW^+rgc?$$TO5;S7RF3BohCTVr3KexR!)3H3s_ez>u)ZW2M-q{76
+zE4`?VfDaCs%^c`oYw(g_xL<Y$eKu^YERBjOQuMJ)&1ay1SJbLrzY2mwKJwsBXANG@
+zdHlEm>v;1GvAcFos}JoGiQjxdz2Q6mI=6bGG%YxyZ+piuLkjaOa}phiH`Drq(9u_6
+zQ9&UupJ)U08uf3Tobv@-lY!%jCgTQN=v9}MO-zB2lE0=>c6*~0JVNcDz4P1WALBiz
+zWEp798-ns};PUy1u<&^1PW^Dl*J+H8c$@$qk4k*ADZR}I?eQV4x$UFhSF~zXp=6&Q
+zO;xv71>7N;ZRvzngdT?(&vD07TBo7#OBUFrnO2UX#CYU6y}P6Io@%n61>DcwX7exI
+zTLnvb)Nh+T<GQhVbZ>aSWpnJ?_Crpa_D$%F7lc@rJAt;aT83+$Zu|oN2S7&t1CW0{
+zLgk|20RR}O0RRyGF97Lm;_m$41XAgLCy+UsHg-p&2tT)a4J&{t$l}hKFH$}Zu(nQs
+z(WfWz$5IP_4kNWCq9tgjNNB1AKDIumrxUdk8EI=0UWgO8vUAeRax+X+m0N9>7noCA
+zZ%LEtS~@y7mRsdD-mUYbpR6aBR}-dmmEFQ-vo{uauu@n`M}HyD&_p6=;5Hh%p6kAB
+zih7r0e&n~(qWY$~iAyaRwK{KS>zq^DS~kkVS~;Hedf93RWlpuWxQUyGi**HeQcEg6
+zPxQ8g&%FJz*nU!*Y!j92;Us1zi^ip8e4Lz;j@LOXHqz5fSFAdEyA<BEs8PS_`g&K)
+zbg-W94-|5&bW}=J`_yU|Vm1;12{bpp=SVFIv2;<zd=0Ms+#*-WS_Tr8OKfDM5Vepy
+zrQ76Z$|cvR%}lJ;U-A`?8aIovQrr5<k#4oAzVa+_gqc-4kS2;<<ilvgfj2diJVbb0
+zW;2r*=}0Xm{E4d;ZYL^Im_&C<SUaNZ8e*ZZ&w;6WUbfx}7$scC_4mFS6=oo~^s6?8
+z0-~>E4weU2-P_sVcVr&6j*FXvDE8~zXC7wDp!rxoCh&*rfpdskX%MLN_*_5Pk3Y^?
+zlWMRVxA(=a|E{i5Qv`6tj#dbkiCTb=4jYF=EE^R$--<JDVE<M`ZckF0zES+honhkD
+zF-$GVD4BV!i`$@RlyV4|{~j$;s8`ZRZ@7ox)3Cr4PI@Tuq}H=di;qI==$%JxV*OQ#
+zl$+T|U;Nzn9u)fv!#!uH-L*G4Qp0GELoI0P)=59<CH4hCbhL@V#cevi6w`<dyw26Q
+z$i>;P5@K5mqkLFDh~{2#fnB-MTiQ-Bqp`8re`Ro9cAD?nRXv@#1G|uj$62@SC&ea}
+z8w_=R2Wz_=(&x}#G6%ufE(ZZ=5X{IC;o7`58@+Q&v-Ry`0gH)oYmRf%+<a0}U(+RX
+zB6#mV%|(QHvF_*LROUO31V>Yy52=KAkdfgEezU3SA}S$#Yb%>^r40{SC|D5$Z-)6$
+zF~@XGSEUD6&=Qv@kE}SvF8RbQ)LN_|80GAvAa@E>;gHQt#~b+Dz^&f(MG?D(3{v<b
+zu8cOC<bseo^7o@<hFkt&UshPnR9||SK0jGsJYPT5A=@Iu0E}QoG&I7eaYUfh<OF`p
+z3>^Rg4~J-k^2JM$_W(>_J`Y8#!*a9JIVj#Nrwv<9t%m73DX^nT^|g{pE9@spsl*0Z
+zq=S*8P%*nxbMMImu>C?Qkiol%_x%`x<?virBKj3*fZjs(8FG}_gSVP@WAP)ipHrP8
+zK>$*^0bLWdlw)IA%u6Uik~XU7u7iIRgfwuC3TTt_hClRYpUBmadIk9uo9DgevXPPq
+z{0tLI>K(xw4hI`*bSy_u+iCoyo8y5ab);cE44c7vGF?|M6`>TA-hwp5d3CP6^0lKt
+zo83sShkg7E+G#-rZ(P;QFzJ&XnvlL=MX{ESP4M3xLr&P{g@+WeKrh5b(iOEwarDEH
+zmWuB_U{gfOISg;?q~Qb#JgIq(b1`jmWSn~y$SnEe__c|HzoG=dDY8nC=#m!t1dwWU
+z)pDPI>YW$oGHN0Q^3}@(VJVbJe1#Dvzlq}WpzJMUsM3J{lz^<Gk%x%ze`VS?&nV3{
+zCLh)N+vFnj>~ly{p3m|tY%f3+B__z%jDwp~Z-E8`=AaBy3H#bGZUWQ|*N``hAMXp)
+zou*iy*|vX0e8gW3ldUF;jtUwL!{eL<Gpsj2e+z(SCD#1>wA>eVIdpu2{H?IsKJb3W
+zs3c0M+PwuAodk5`S75OtW3*IQk&u-Qjt>{;moygLfpg$aEJ0SXLL}hAP->nNqjqe+
+z7olD@CY*X?LHR@#_PON~{@J4&Z&lf<F5{Av|HP66H<g=SvfQKG$K1Y4$H`BL&rdVl
+zazO!qt{`1rywB`qD$v}wM*cIPw@H0wd;xR!h``*H+J37z*rJkRc3p(q6=>#)4jPsr
+z^deY%k65fo%=hZP-Q+=lYzLgfEy&IwSOzx`WEv0Kr~_ZURDY^UvA!1+2j|yD3^Pw)
+zw3q;fzJFm2FYFZ+;(ji#(b*Wt!^{G(i(7jK>Jl*lvxl8=Y|Ik^QQ{Gfm+y1b748&@
+zvuUQZ=HI4Z{28W=ow=l+l$$xI=QF)map!~m5=g#g8Ue3^G8p?24t_U%^(?OFsu=Ob
+z*~z%QesbQ!9~DKZRg=0iSFY{^@v|vE0>9jYtxvX)#UMb3y-+aHc214O^^Z#y{EDh{
+z5IluYLAxje{C&*Pj0WjZ;PZVwadU%SnN`Fo96!2DCTYvbX%BQdcU*MkukVvK_8>&8
+zuw!m;A-=@WsjDfT)TlI)7E9(yTg<a%`PusTK)V-9ShISvbtb6)3lgbHKdnD%>tv?m
+z2o;11h}W!1Ob_kaE{{BsD96`Ut02XCbjHiqr`}tVh#D?rHh#mcBGEPjzwZk7qx8p`
+zekBI=ZNwF4BwbZ7v)m5KL_?6mQhBp8G^)MN;kHm!(E%aa`hg`5J)9jgCy}Q>QwoJX
+zKBNkz&BDj>;G%_Y`MRwcbe=?-%gJjxQQYTqC+_YJ83hO&#u!3<oYLBW$cBPqvOSAq
+z5ZIWNqJyM<*EEdgd|;KwL`CEX_DC@68Ac;adRuxw*ESeICC&VlaW%?iyu5%8=E7j>
+z=mIvBD+w+fl7JFz8I$n-<;FqaLzO5j11%@sM9t!~5?cVme~6BTp>7j^)`P{X2l(Xi
+z{~_$1qC^X}EYZx9wr$(CZQHi<q;1=_ZQHhO+nJ}&ef6sDt**Y^dyM#vu_8VqV$D5K
+zz46lr?5Y)iJ;{&#P_|dzKNu>e$F*BNS}-9Yb%d!7F*-kAASRo`e)2UHPDqpFTw%fH
+z4Cb(If(*kd9^ouj77T1Qoj22>IH||`?NaXZK3EI&r0kNPnT#(drxe=u>J0L$gxZ{C
+zybzR&^41CoBlT>;E&g))tfj_umo9IBZ*u<~$2QvTxAK%!;x3&}or??%yrzM-aE7ot
+z_ZHU4`R9VdoPx7btQ>aI`Th#Tv5@++lc20x5Sza^Iy>TAEErxL_mB~q&0@w0ZFdk%
+zAtDDZ0mzmW)vh7PKoIK5$@-BU39J*)mSwhD90IVv$OL>%@<@@7EclW)1aY*B`cv`g
+zdZcEbKFD)WcxI`9YuocyIP$gK(X`1EaYZAjyxf~iRy$Bcy8$<?_@Ru%Iu<jIoE0?T
+zUV$U=hn8$00;jWl#?g|a*_Q_2CiSz02>Hsw+R!7K-f2nxIq7lzC&qC+ZWp8#F+<GF
+zhX?8rZ9%_jdd=7CK4{SPHgQZloALS<h-8k9hr+8+5OlObt0uV_8(^v6N&3ZhQ{GC4
+zGstt`52(6&7Tk-t1DDK1bZ%V$n4IwQua=#(UkW4x4XPo+2O9HF@7~i!u{Y(vva7rm
+zvrv!RExL+m@6I^ErFi07R?oSeMVn0asS=n06OR!+Ge((1FcqIq8u|W&|31JdP}t@E
+z%BIEM2w~_GQ`gE*$_QttC7yYZH<QVmL{ce!ts@UWooo{Wk0mo_-lrH{Qq(h`B2htV
+z7?My552(xCOe%>|ZnUTo3*^pSb{kR$MloHLp;z0{LV%2HJYF`6e`Z$~#8L>A0p7Nc
+z9l&ozmXLbE&OV^VSwjvW3DupiOepHbnDJF5r^O<Tx8jZDUkprhya4*}LO+d_`Yp0_
+zVEh2NjIaa3YP^|$m_R7C7)T<g0r$YY;8259Fri2QiQSNpbK*mf=DqC};e9JLC#_hA
+zhkEgw+2Q<E^w(Y=lj{-I?6C-xSfea`2hUm+(Gp#G3}C~E<s!Dj4Jqnou86l$V(AU{
+z#2+q>Xb<qv8nX~PgLd0>K!nr8Ls|?8$lRh3a8OIL3(tHw7u7S)$#(RGt<do#1IQBG
+z_ai&16*+2h_%R<g+Hk^|woU`0-LXZE8Dc9#j!+>Xym2z-hp<>F(+6b_)^%qxf*6GH
+zSfOQiIaQHU@k4daL-A$E4&sIV`*YE7!Mm$bzMff_3TmXf6nOgHIb1I^fT~Vq#-sg8
+z0P-8YHh5He?wjb9KZEb1VyC}-j~jV&(4A=`TeogMEZ)a_clhhsG<~hvZ}n<%s=40~
+zTvk70VAbQ4zrX0Hz(%AO{dqI~av(5e@dj+bBeXK<%N7Jyw>Ggrr6ELZHH>w)*5c_O
+zOzB%?yC){+0de4hx`EVm8WE)_&TGxUzd17S9KN6|_yfci7oU%$&!6)<Ozm9~^S4k{
+z^OnY@r&J2thj!eM=4M2}6m%uWM^CW>YSZsk9FWi%Fk9PQZ_OvkoxaaT<&Mo}MJ=k0
+z$&qC1*t5Ul3wgvlt`+*(#yYlg1{;CiAE`O_!EDd5J97tNvK;R)FPg2O9%7|SNf+cF
+zWxP!^ffVUs2S?}WwN9iNNm7Cy0K|<`oFGX(d7uOJ59@Wy{GdU}%+l^-QzzV_4sbKB
+zj_@b|*v*Ga9jxU5BOJC0c=QSoXcn(Hq{7dq9QE$wn}x@z$S`>r4sUP`6itDXRlP#;
+zI0$Jb=AgE^T!khB%FpxAE7&*M^I*sIEpVRM$<LUO#Vc?U7_j(MiF4s@L#j;CDJ-21
+z&b^#_#Ff4~=Likqn!f`|u(c9Kg?%PcR1@VDwPX0Z&&uYJfZ_&7SOJ?|JCC!#=mA?~
+z#JfIR3WmL4(&CR%qoeh8%~5|*@{2WV%a#u_J?(%-sJx!V$5C~`WC@De+am~6;Yo=*
+zoLx=sS1r@!{?z=qFdz{q7sDwXu;|!DBd*<Ab>rTSrH4$D1l!gNsqMTT9qhT>8u->N
+zgY4GKrFO56zennmcS8lJY~(_sq;3K!=mc~o+edE4AUd`xl}N1Ic6Gt=bI{u|1F*Q9
+z?v<2JJ&6#?OteIAKj?$@I95v+NX{DO<IZ+<H8J6+J+9f$hECH8mB+Eg&iqwE;;W5r
+z<XE)yPTDjU5}!vrM8FnxGlRcL-dgQ#o7sJ@m3<1BT9sdy#yz2m`MzbmD?viq#hvUa
+z+mHHehpTiS?*}QOnWFarn>}p1RnNp|jEmeBDb=$pdBUJmD?qbPlVA@HJLMb$vr|~w
+zbdoh3SLROs(1enF704J4RD6O0Lmv3cZ+RA<9Ha6gb6%Ytrw5GqpF;f3WtmxcyRTyb
+z=kEQc07~UYs$^q~)JPaj@f2DtDgL-Rj=hjF#vSI_Kl!T`+2IdLUHrKRsD@BH<0)Bu
+z5;ouRsZYU@y`cK5_U204$NICM{jvydb8^Wp2eNgu>_!qx%bQ&oJzh(W$~{}SWao~`
+zXUMvHiO0AxFUIO9a#yM*JOlGl(P0$cI9Wc<hf1?;(cJ~Nc1e(!vMluaG46T(ic8`5
+zecGE2<(pB4!W^#VSZ0OsS=IAm3%=Y}i6exzQPv6E65v#rYMb%Ww>4g%%h*i<(Mnjx
+zw+wSM-m_c&`b0Yocon|_5Llp#Wqir?+|LQdjmzR6RXG}W-klF$D$Rk=1moUo(jmId
+zB42ptkp*$wb~2AF2FfJEo_nJ{<OjL`WE~YUi6c`N$l+|aTY0PKwbb|XOyj6)RCnFr
+z&G>L3Z&XP$YaF^V_EB2J1BEA}%d^O-$*WzUfqK_J*B0j??tEW!4$hnl#k<frNmj1V
+zWXUZ0j64u4pQpZ2pbcB95bhg|Dw`?i_}yvpC1dVCRcCpkAWZ1pC*I{&@R9lV%?~&I
+zNAI7o#y=!B{I)o+AAcn_)qf>6F#m)#Ol)2M6VmuM>Y8Y^zeY^#@ZPIxb+C0QS|Xc0
+znBx9<m)kdkEh3AkP{97uQqEJNsLjO5Pyt_`IRql<35x~4HpU1~T}@}~*)yjTRGg@q
+zEnf=iuTreW*_!o>En6Ns7Plmq>oo5rSy!v7+8NvvyjY$hKX<Lkn)H(qOVYJUKx!|+
+zTGW!uFzj%zVp?oDCttoq(M_pa1DfLsZ_BOG<)WIh*@}jJvzM9(uYXB4JszntBkuIy
+zrDii1bp}SdQE^q*19+~P7=wsWu2{N5Vf8+SjC*TAK~7oK1|27KRiv$#*VH74YP9%Q
+zlvH&>HrS8e0C$@2Kl5<?d15=D>=Y2nnF3J%)hwx1BAsB}ywQjat18*}(;5G2Ah4BM
+zKq7&J+RI7;2HJ<-=6lF#bovPsr<#3_=P^Dru&7&tn0dn~fe80IsaDyhCvt%{1!7Ps
+zoQ4W=U0e&f9O0Suaxo1MsXqDjo7cmfgYJuw>+)4bwHsdbd(y4>AjRnXK0Leb5fR;m
+zrB#wFkW86rCUT52A8m&ZYSY3vmt{bmUvGRN0Z&{(2*iH!688CEPMs<d3h#we4ip8g
+zn*3NbMcF|EzHO6&Xf4`}KmRU8W7sXapbA&k4%1~dii#^@TvMF|!*%r{2Kx*eqE*l?
+zd8ZMEt-<&OkTjmJYSah3Kc&ySJtVPoFHVCA{P`JlfE(=(#AjQ+dba=ofW`qHm-pmQ
+zw==7kH1a5Zhz^Jbz>7C5OK+vpkXF;y%?xdAgTd_j;i4h@mXM*V!ox_u)Xg&}%%Mqn
+z6A?SeRYJFMtex&zX_R%VdjIX}o45V$vT?^O1h(y>J|ouhj3Gksr!Cn8mRVZro+l4*
+z){Sx@_PHm;sC*^f7!I-N+&q^|2qP*X=}dq_dz0Yd?hbU1aIhGh3+V?e_JV@H(4}>Y
+zer>=_e9{&!u)(=}n9(tU)t(YyZD^8l{G7?0OLH1N$Kx{mvOnDtfER!VzFtuEi!x>k
+zEb&$4AX{Ld&@4W3>d8gU?Y`tK;329ptv8fTZD7iC#y2W;>qG(8xR9*OP)sdom3D2Q
+z%os-nQR`ml+zg`p$&9v)RLxg-1Ub+&$@n9XN@$LxqUiAy4x}vEPet|Ro)(I^bQ?|T
+zIJs7=SbJve&v%o@tI4xi{l{JPo9j=zTkl6#2HpDcmf9t^k<)JZ8I2!(??jC9u{-a_
+zOOmt)u1Mitk*h2be2ik~Zgg8AcC$7j8B8<o2-}4!_bz`Ok8{9i={}WgP(DkWVpohL
+zL&>2?D~C&7{;s)y;!>CaO5aV}{)#21fmfI5jXQ@fxj5f^4fzRD4}uoKse0pkPGW?!
+z!&FB1+-d=2e4Lm9llwcPaQj<)-X)+}sOw#X&cXMEd8g5@GnSjo_ylU#<Fzda&)B=q
+z`LKy4Y@2Yxn`)fU&B0AOsCk~=Sls+_gXte-oI2l6P>|hoMRujw+Cm?0Z*04gMiEGx
+z{P@1WVJC$jA^bM!MFZ+gS!^3f-BX=NSE2>pwYCo4jt@}_|AgkfGv4TnNornK5t||Q
+zm%Psfv+;_8+7KZGJ(2UAmp**l=05S^HaOAYHL2N1_aB|{6MZOxfRJ;-1-812&<)u&
+zy@7y%G~MktM$8sgbbQXDk(;*c#kPU#d<0C+`(iF2LYtpB+lt-zCwkO<7v`v-ZH7N&
+zN;=9p{pSoXx~P9*F_r=YZ<Uaz5Fo$NS$`c8`t8R&(sLR9(2bIBL9{}ZHsmi3#S)bj
+ziYH$HD6^-2-Cas9XXmf-{Tj#)=M&g>bpJT9gcgxg=)imXahaO)TWtID11zSZ*1M2V
+z9t1}r?4tw@s+}0_a?7_x#wO0l`zf(fI%<)3OwItVKnT$mg^#c^B3k}oZi(rJGADjo
+zdk&pNM~KN+7!|^3WKly^rWiX~FL198*B?}KFTiquMpBZpi1{cqVja4<YiQVVTYAf`
+zqv}4aMTt*Zz*YB(;lHr#pK%qr&V*SYD>)y(FTwSU<L0qjh>8uwcJ1qL?UIZZ+<CXp
+zc*>ai1^3SjB=s*YUM;g1UIq;SP=Np6e927Qj7;qRVN2%ppZAFE{}~!j*7)m7#)|k;
+zqvv12L{^@hesuGft2MIf<ncaNl-PFdybuK*A2*Z%><nP8F@4m#+X!{1;+pU%O$eb)
+zjr#d;1y#CueiXXlt_0zdTOLRkg{nc3twftHZTm7o;XrmRfvmaomLu`i+MJh*n>;6Z
+zOo^u07B~@M6CbYa@%7L$<7JafemuT}n@L9FDdy!^&!Vb4!O^aS29~9Ewyo?&q^*E6
+zp_I#oy8PWApej!DR{$l;Sojza9IUN~eJnwK4r%>V62uQ?;SIm|*4-cl7=Bl`vRA|i
+zS2Hy#lQ@n{nMQA6I)MnMAu4XrCpam2+{2qh8RtgD)EvKVBjjhLyimKj3DPjwkGjFe
+z9LkR@CvlTB<M94Ahw_>@9;8C+V-DhG@&%=I>E*LcuUkd>%eQ?a;<;lkSg-a}HhGg|
+zvMp%F;aFVQ_lY`R>>z<61TGCz?yZsMPcXHl`t_B(B7%3z<-l@g-7sZC)B^4c(q^4r
+z8jW>AdPM?eM30(=Oy-{46m<?G3QE;q*b12xGd@viAB@M|QzeDoN!OUMYA~v~(q#O~
+z-b2TO;WYW7X=r@oEVB16GDN|Rerj2dT;#Beb1q%{l@p{|9QZMDz*;6A!R+E<yU>Jk
+z-bUBJ;|2ZG>OYQLU54LW2^ZSX2ibl%zn0~a_s~}X3c(E2-g&u;9UZr`X&9&#V5E^j
+zWP4hjk8XH8u<vx7Tn$<MhD_tFO!dvF-bkU+4EfdL-z5NM(Ls2g$hMD`HqgPTbfEsm
+z<6{Fe9&3J8=~<W;c!uS@Oea!L609-);D^n?zd$pb1N>5@olM3<P{foGB)+I7TFGpJ
+z`#solr<KO!E4z}XZu?yx&mtM%O}S3umEDS)Rk>R<3qM8yObO1r&}J`Vkj2`ujYgZ7
+z+J{z%aIdO4jMrrag#CyNFe_q`WygF06$F8-W>sx%XqC;_$ss`zqlybVfmjU{uR4ge
+z2W{~XIIp|?8T4%e$TO($UW7}?SjwtBHZA-i1tqj9Zz#DTUb{!lz_?%x?*bR(MbGY?
+zFheJ?eK;5E;DBK<3TJ`GS45Y%!+|_^pY>NFyXT+b$HNV1Oy3?l<TQ#Jv7{H7x%$Va
+zTd-(5VgI>hZCQ1|089>p`3eVFWng}PR~+T&+E4VY@U!$y;`)Bn^9(wS^IPaoe(8bM
+z2cygnb8rk887#q>MQ0}6jt;kHhevN%Xt#&AQ}-`icw00Qkx#r1j?BOZj#3!))P;Fb
+z1?m%!H$c+8zzq9N>Ys>v>}39jrZ^7fV{T3RpiDJ{p$v9*C`v&3FhG=oWPzGV6_^+i
+zKHqbk^njbi13JA@gq?aVI~aecGD2l-{C;SG7kBMmTUc|{xVQLD1cKR|1_SKQ-dR9A
+z7)E&9n@Dc852wsH%UL#t9wf!ei#<6**?Zh=m|i+P@xF&gfTpL<k(I2YFbv2*t~6^o
+zhjN_;H+;>!Y$vi5k}VRxY7P#e>Quv(s_-^et8ju_YHc@AhVr2EQI!CxvKS^XRHVT3
+zv$T{j#AzZeL+t0bP_f7!_U}>oaSW;j=mFs~GQ}z1o_Cy#lrVZXU$?e2AHgIyojC7(
+z7qC!X*W=JxOl$AE3*u{ZPyIcs?0oWH`u5Z9@$R*jVX$Az#)#QoB)r_;6Fynenx`WZ
+zOkX~e6*}H;o^LUYs(3&Cx9s>3$s7DxZbOcFvGA3Xti+h<Sn6l_??!2PV$T9fJTyWV
+zlU5?=1QcwhBbxXEYU#|&qC<hi1exV3r6)Cz^qGA1%L>y3@$~IkFWM`wl#aff{E$g<
+zVR}m~)vR2vivmpA0C}pjOZfFl>Y;gCD?`QA#I$Kul`&X8O#~;#lHc|@bTjVczJBO`
+z)H<NF?&!VJy<o`=i4k&|U2qiLRNU~8(J>kVgFDOkFe(zy*KGQ&_EZ$UR+M~FRw!x=
+zacMeh4OfYZXs}n;w<7ACQ_%bx=aJE}L|cou+Q}AfE+An~3Np1-u!xP63ON_RKpnk9
+zpF*%Ceox8-T&|>nc+H#THcKD7N=ZH|LuD@$vRIb7{~m`IjVv<ppN`CLCL~kG_px#r
+zrg&kX=`J2oP!I#60q7ww?B(fir33}&P1VKURQ#lHT^eDj(R?e*WvgKPJ*HWFPvAm8
+z546^nC?zP5FD@>DG9_L_zTdr-*K}7pTM_>2;^BPF!6Laf!raQcASuV|WYPAXbO^3g
+zJOJV;hD!qyC>V^NK|@t*G-6M`+Mn=D5-#4e>zO5g+U~4&DqhR!>h48Y7euTy#aGMf
+zMv8bF(e`_{V{akQt%QP60dwMb;>cw^iJXn5z`k{sVbBHAC8StT#kNpslz@MTBt^eW
+z60#pw9Sg5#<#+D{osz7D@l1A3PoairmE>?Wr$WS0d4RPL5X`;oyPWu4<P0%Jx3V7T
+zFK7YVpuAE^+6aC^4so+Qy|xJAE5WVhB?QfdU<6bdvt8kAg{<0Et+Joe-PK;*?)EI(
+z==74M+kK4;pEQ+HmYKBbp-zqNT$<sjE3;cZQL1B64xYz+-qa6Zx+VBu7R}_jd64Xs
+z{r<vz{W^?4jrmG~>lb4VdxrV>-$609%^VkK=E3{mv2e&Wm|E5X<3>Ogp6+zV#KUWt
+zT;=SDV?`w&#)aykl)my2wQBm?)3$<eVjGEf43sLgb7PmfhKRmU00=oVV~{ki0qb(+
+zs90*SutF{|Ab$chQ==cmK*XdHUkd*CS1s9Z(GMz5xrgz0WvqnCx7~pdwnz;@^%!OZ
+z>CiNqa%tWo+CEZC=sKWDUUf`MAy!Wwp?Fo~O@?xTjQ;A#e1O%|lGeQG9R_`ED6QwP
+za$+q>ry2aXM)+{(7rtuQ*0*NW7}|-86o^Q4!Osnibw~<g9v?!}(#iu0x+5pS7Fugz
+z!oYOL1t_-A2Tq-htOC5f^b7;I&683@-W|Z*k}S3$u2F6#aE7WN7R(BW8118zpLR&R
+z7!o<-;9#@>kVq7gLD3BBh9N>$938vO=oE~^<9$Hvr^PqLKoJIUambw$cvFjR67X$`
+zx|ZIl{p=UkE>I(QSTD1L)7t6AtFmoZUYn1fy6XB^xo9JekSgHvj9>zcWG>1$WVkcx
+zrcKtZHS6cO&t5@0@MQKl?K+2($c8MtMN}?PRY6<_fkkD3$Ub2)i;Dxu2*DcKmBTY;
+zgMZz=SYLcS(7J~W?cz(t!IWrYm^yXPQqyfx^#IH60plvTX?>v)05UG*_d*`i1(GID
+z#o#Kdj4i{>;7rXzK+Q*A*T}eTVUoIk7{3%jD!C??nzt_j#ujuT5%P^?aIbwFas0++
+zba-XH293&R`7JrW1+|vTP&Vun=*)+`dK%He@M^B*fRs-hB(Hn4MO9HkgEjS(!XAUC
+zgo0KC!O>%{lfQjnXRdTG*9wbPG%d62M+-=EwwE#pZafBO4mT%0I(9tt`>^;{2^D5$
+zU6~0X4$G&XP#Bq67!b$Lu16ugXoDPvSwD_he0S=@kHpsQg3)em{6Rc1J^FyMA^k}h
+z-F$R=uXFP=$5ilr<)kDflc>=W)1oAwdwON5m_Uua(a=0*uJNkRJ$~pKCN)32e(nud
+z6W7GZN`UlIJ7O=H+DX~O9WYO<H{O>(OVv!-K-d8N*Redn3T%u!Cb4eMjoohEnLo~9
+znoFUFJz|Nl!Aspy&HGFr+v~IJGsjW}uznIQzFb@nnoeqe{7IxwDSO$mCxxf-EMH%V
+z*w^|Bljt7&(m2S?X1w&MEqIS!r0=pw`smk^wk<zmTj)+ov_MIoz_=J@jgQ-l!v{dS
+z)gAMfb;ml>S#)jt$!Yi>-f-O>^_=UAlJ-M2i#61)H{NicsNj`Eu_{!YiAGPIh<v}K
+zCeaNnhcsFdWa6wy)Z~l&=MRPFSd2SA&bqLSO9gJ`*^I7aF~uKzlOX1KWE&@RkQ;{d
+zxwZ<n{Ev|5b=0lzR)Sd4a=c_wX)|UPd~uxayJM>uz1AiXH8&kj+nba#+l*Jm!EnXL
+zo*AAeSg<vDV+ZNA?y$rS<p$_Ohza77m(Yz!`lDOj&auXR48<Wpvk>)Ir6zKVM@2CC
+z+0agMihP@_uzN+3vUt*IsI(s$?AKy~m^m-_2Xzo)!Ien6q>pNV*2le{AWM2lbJ(sj
+z#^YDJB12)s#{<<*B%Y;Aw@S4UJrm;SiBoT;+-5}q7{6<>QGV2G%w`y&Db_O^O_%xP
+za>~8&6sK*f37=M>^Wf!QP~8@=;u^j>1(8IP<pMJN_CE>heUc@a8TnIX3h+&j%m?s#
+z;7vd?>^xXyhyP*={SVlzgVlN?90~y7?r+!gFSgJoZYD-9&UTLf+4KB6>`YV3Zj&9=
+zXSQ~)gx|=_#)T`k-_);Q8X8nx8q6H^Qy!RsW}cO-p^T)QeW&2B6Ejz6lCXqpi%&RB
+zeCX*%HdJH1DP6;sGFCT=haT4Du-K1Fy@j6p^FiY8C#IgNVwZ`Q+n+tXL6qoKlg}Rc
+z%My~yVoygL1&tIahgdPR`{%|A$VCL#kK=;_Lb_t7gBy|KXf{{V72k@jSvy)_-qh68
+zuU8=wWV*Q$#2Kyg<ri{Re32!QwZjG|-LGN|CYj?QUEE-I)s+jua5IZ$3v6<C6^;UB
+zEFE{UE`)bkRjp?D26Ess&E`?ok``62_p0b=P-qlOB3wL|;_t)5sdC3g_;v~@d%%vT
+zY-R7y&T{1Mx`<xUd_~TucNpa+>SteNWJGA}rd^0T<)n!BO68iY#20eNbCUABac?6P
+zDkg7Reb#olx3gFyoCuYdde2tGXeE$%+Guw~8JP4x+vKO`1XvCsi^01THWHfaNk06v
+zAE@<>@E%0QE?JS!3d)YP!t?4nHlCT;2MH~)>{UVK7VXsOYMS+kTuP|I)K}*EpL@V}
+zfcF&iBa{iterX%4KWTA*k;(UlmHUCEYxNf_rl1UQ55~!5)$52b7Om5|Do3Fd`AMV8
+z5nuYvlIVGIDF}8KrelEKH@`9WkA#)#+w=6)i<-fP70S=VF}7^C-CW<B2`_Ck90lRH
+z+jzNQxw3DCX$gX~v8-L2C!R%51?pt+8B}4Z+z{iSI91YM9>>rH{fS*v$UEUDPS+*=
+zl-`Yd)S|S8L&c)i&u+KOfMvGqVr!)~Q7MN92CkbuG606TOfKj+J75DVFhtd^f6fM3
+z@>7h38NnBO61HmI$Dn9KSLESWII=v`>oPkq;2$;ETHEwByi82EjAM2|Gk`mZ+|cIp
+zLl|8z`@oowAhPcG;im6QmX7Jt4}w-OgFUF^9pgenyK3wC;$QARKeLF?RwqtX==YIA
+z(lF|^6ZjclWdS*2e-43|psnd3k09l>5&86hYY-`0R#G@ZbUKO4ku9*QO=_b6SyufD
+z%eG-6Pv2_c%6f#h5h4e?%}>pvdpIhqBcK^ja=arJC5yNNYkYzsgzR9y8Y9HA$qwan
+zh>DLD*{;hHu=5+whlLlPwWI~-20|#!i#8N5p5q9pp3k|K>Bt+yP|&Qdsm+DmOY^#{
+zQ&K6VXN0<k&*Mb;Ek8d!*7Wqw=`NVNj&kkc+YG<kGmBPB{T`m8?eG7p#&&R_k#bu*
+zV>8xZ{qSWl1`jM|Zyx85i8_#B+DYY)v6E$(tsy<Y=KZr6<<<d%JzW}tJ;-uZPDKno
+zbWDf|;)Ap$CJx6{Ls14{x;<uBWMuNHdV39txWMBInlTsdNk&UEfXY6TB3J|`&l%QT
+z8fXyW!uDt1`o4BW!rtTbewhK#@f^Z&NZO1NiWh_~@{NT+47HG&QxCd977i|Kg1{9C
+zJZFv&DG(%yx=j^;M5mk(M50oDM%l;@+P?wJzAhj^Gd7O_naqH!qO(Eau<qmu!vecs
+zP?r^i#Jbs7=$Gp^V@j2Fe^{U-JTX!)Iu|viE^+Y|h(NdWf^yj^F>-^gvj#uRlvTbZ
+za@P=Nia5GW>D3aRAaNqCKX2u7!F1Ky$|9F}8q70p={Kd|81Mo3;7}sxFIEaL__`-z
+z@q-@=?rUdBpFnBL*L>1tmF*`ohkCxwF3)`9eo=;I^5sTKB4FRoO}(qv$*6|tr^Z@3
+zuPvzisaw1gZ|THR&LSh%f(jmcRK&Ztp-=fF)ssH&;7l-oA^Pp87?X){3A>?F7P6wP
+zB{9k9;FdaVcWp-=bdBHk@FKb{$guTFgbwN5518u|`9m;(1p;=g_9>9U9;)e-#FWie
+z>Ef<C-Ah+4s@+7hk)=JwNb!^^^GjYG01EJkE#(cyuY6I|8;)QGSL4m?{`vB2lys6u
+zD)Ptt`WQgN^20BZc2;FY1rra5G4XlThO+y~4jOOYV~8N_8@Oc2w`%kPm`GYi5o2H)
+zzJ1_Znan$>w!sQD9S;Fb8SqAH&5{Jf;W%e3NR2JnC2N&sL<MU^#D07BBUPs(n}_gH
+zgh)2m=$y;c+%iWqD%ZoIsFSx<EHBX6o|{EqKKy(ks{VRP+W1S$7IJXNrqwa0fC%Z7
+zsWsP;{-^YWE^@!%;fL*ZJ40<GMCwPtU@=n359Ga6j&iRX&gzbmpU5v)pnofNbVO6#
+zc^AckgVUJG5IOMjiNxqNJB1Bvf2l~Di*-ecB@UaeHi4^ru<$|7^AXF5??f=j#DBS=
+zg2TObBfRGFdH00t{4T4PZjMSBC!v0f$>rQ*F^e_{1)PMqv~vSX#!?56LAq;sk3_^I
+ztIORLLuf=Y?5xdRf|D0;OLGG->uzhZGxb1+xt_Axp6X6cCgKu!w2CI3!3By1);qYX
+z$xM4Qs>UYau{2-`*j+Y15zM6nUl};6r#+#5+^xFCZAepALqPBwDdx)d+R%UhptrX<
+zsgfZQ8dL<;X>wVv1~*D2W-QP$Urq7PGD2ncoA}ikOMT#VL8tw+{g6K>na3?+FaNto
+zHu`dQJU|vycVRiTuy8xv)vG8FVvqwj;W%w^Q(^lh*_E$p({3<Vgqj4exU=|m(B6Q`
+zq-;ulf9l~!C#!MD2fB}ulkzeI&3n>^saX1jZ}tWw1MV@@!H>S4mz(fqBpiqAY_a}6
+z95qEbppYC6&_4-N)BFB+jDM~g*@<{m=hAxV_7}NnAC@J6m<6wD^Nlh^)I%%RZ*1pM
+zb=U@!!LjhWt;j{Zbd%IafuQaLZgQPPt%fw)$YjHd;CuENKsI$6P8v?^dF8S{(~DN)
+z1PDb=w3^RA2=v^Uazwfsp$$#uymK}kWYg0_9JmFW!l(-b3W|UvH;c;xr=X(R593ZL
+z&}oz+#YH*=6oMf0HAT#cZNuN|?Rx0X_*wFmH}Y;A9^OR?u3U9(7l1`Hys12MwJ6H@
+zJL53iEw`@CaEb+y*CDeMYjB}gVArlm^N>VslLR3Z)rYFZ<jjp6h2y?wzxp4%=CGZ1
+zp)Tjh!N_h1ibLRSw7MYE=0<R)1vYb3xrJg!IOCfv=lp1kd2mSU?wH%V4n8dCfFbxC
+z|72ZY?RM}IT=#J4#E!XS{ElSzmk~2nQ^l<TUByg%AeG>9|2^zTxLGGpaUn-kUb!<S
+zqh+rCF!D!hwD=hd2dQWJU|VcKmpef8h#7RU?cwA5@mr^?D-4V>wQ9$aIS3{)PH7L~
+zGX(Tce*AVY?r>&(!0(D<Qg&6+AM_XPtA+~V2k|lP$vAH@Hrk_vNE}SVNVsCAgIUm}
+z2gPy8b%*y8rXC|o)6&dR=okl4l%`ket8V~<Ft-3lHR=!CImE>$`<AmO8ih~XSMncQ
+z-+2B_rn{HQfF}Zx7dD!Ho6tiUMu}ZZuvLz9ZaY!%@l>Zi+bo37VVLb<nU;`C;<Rki
+z2P4%mP?xr|W_NVLjE|-4rH!-(G%s|yUce5urH6UH5hNi2#Pbgv?HlBf42`DT0^vc}
+z-?$^vr&5#6MFa+P$na=5H+=6qq{!nZe)OXX#Jc%2&|j+&2&e{1*cJhDlH9a^(eNi#
+zM~z=Yg?~mr+wa{H*h?;{13rsVWocwT@x)%~6YmA80B6e@uuPt^(z~FZ$Gxa?QV^VC
+zzf0DEZ$?D+_Xg-)Gi?O8psSq2`n$?6(lhW{&B6M_UGB_lEg6Ka*vKCsE-G&vISAyb
+zI;BYinWK?rHAlv09>0<(1MO|W#65;G){Q@&BWvOe_UY!!7uFB8`mxf_`Dgw{o_Grk
+zSXs^!KKpm5x7jftX!SgnfD_D#^$~Lk33bI=B>`0Ir8IZSkU0!2^SHDP0Siz_A|S&;
+zgtsJv@7Qi|*ysx^^1-S11wf&1?Qo9dt8>CgZl;5qh5yk0{ENrfKOXglE_5jWNG1A&
+z`EMR$)^-NQCjYM|eX`nS%qA<m_p6$8R%kO|ZJcfnD*c>D0~GL@H6*hDZa`mw$b_jH
+zqDq41D#gbwkI=;pu+6uj6n0K~R=SX8YIA)~l-AkXWcFw7n<llRm82x25;EoEhVl|^
+z&&_TV>BX${^4Aru3~qEUn+%E8;>cwIQ-b9#Hfp4ZD){MHKY01o#Fg*p$n$i-od#{{
+z{-^707fO1mIi*Ssl@89Cx$F@P%9><AmLn`~E%tS=QWB{UvIS$mr_*oW&-?wso9EBa
+z(9Tu7ogUvUo}He~LK+?S{hY03!nA(4{zPpKl_RVQ;qr~X5&bWBt(9O(m17Xk&c82J
+z=bFYkEe*<KKFn}{Wu#CZN{b9e%ZN9Rs^8I%#>sZp!9_kjFEDlI8pxW8w$v<RHohgu
+zD$?waIu^!CWzIsSg)=clI&jCc%`?IZS%5=6;3h`^j==h|l8iBYl)%dFz_(*Z`|qV^
+zO{3xS!k>Goj~8DHlrm1ycwzW5R{OCG#WKA-GR2PR%lTq)mmcCTxDfsv{m+Lz=qe6_
+z&%YvH`$VlgR7OxO+Ow4OgGCMIY<bWmcQmZ7`bgBQhK^t6m3N7r2%3~hxClp-@cm(2
+zHyyPR-}cLN#M)JSnKvjyiRDH>UrIF`#~0;ilbCa;UnALg)OVEoufhM&d<aV7xE6jM
+zGBYqLjn0clELn?ta-J9Hf7W5{>cQYBLPd&;6OK$_d7ARG4mBOHjD{?bNn1`xHM7i)
+z<x(0+Da_TH5o<i_Uu)kkD{x!EcP@lWj)@4<`ru(CA7GER(3{U$+0`IeYGVZe3DY!?
+zImpBEJkn%1<9=d-m@$IUz9Xr&3<Z(xroZj{kZcDAD1ZdQKDi`Ss3sp_ieX?Qq(#y5
+zI#e4GMMQXLKpSAj)(67FzO-+_tEKGgtcYL&lZj8$kGybCUy*F9)YLoJi!2TJ3nByY
+za_zKLGOi5z|G`+%ZMK1Uh~sD(G&m!38b=U2Nq`XC6w<;b={E|HU$Y*Dy9}p*oY!>X
+zd4fM?=KFD`3a`m(@OidewAGm%=$-Z=>JTz%Sb5n<F;&Ev=uBVsh5eM}_8-3A3q_Lt
+zl$iqwcg?&q4>n|XLTtI^c8x|Z)gP7Gbm!F1Y5w3+&h2x}pHr2=DPukpuN$6_x}VD$
+zb2cwzLB`MRH=rwoutgN1atX6pm)i)*>FgP`D{?;$q%-!;5vadC3fR-uO-P?4rixOk
+zbn)GpHStF2H6`Qgb*`QP_N85bXRZ+A#~df{M-45Pxp=f6`zi^Vj#&#iH?LZpt<aC>
+zf`HmW2w>sPgQhtHplT{XgKyk8n~<}E^GdpMP2=p{Y#x=U%g!Y;$oh?7iX~$x-ODd(
+z_4L#p3Ka=x)%1siC#)88U-CGahUF}v+8lDLmbW_{X9md*UUuq0;QfkN?edRf`aZ)%
+zMgv8V*s9R5DOe!xxMMDYXOxAD)V^e^=1e<G%pvaz%Kmw+8r{|IB=M;eaQNed$63+Y
+zR{!X!tkTfV{co!e6Wffi3=|I+3IT)n$7eV_(YH2o1Ixdq7;<s{yd9rleZ27LH2qqL
+zrj->QPITr^mDsAbr+K72WB$#kKPtAtUU%IKCK@;n(-I~4c7_~XW7!6^$4wtyEZAw^
+z@w@yU1oZ;rM#=zE6yL}X@z;@gdU{D*7Hlm|U<zGfL>==>r9wh#8(`F)eAHZR#xovS
+zl;4_q3cWT4CL1Snpfohu^e{mfl_k}6A-I->-D8HO(rOmLoVgE^m9V}~&Kx@?ebsZV
+zQPN?Bj1ko~U_H&KiplZm2^=ijeo9|#>Yt>PU2se{oiGD?Gr$n6Q4+<v?U4xK;E2V>
+ze|3la$H~2g(vleYH=%3v?|}U$i^;~o;=dBQ{tXl(6C(gQKo2kS>J&kcNuIQY&&77L
+z6<HK7Yc)5OWTAO}O;Zx!`@YAQ!vH$dHI{f4^kCZbI;5Z$IV7^GfQSZWpOBAsPD%|o
+z;Hr$le1h8O{1}OyHvOwxQZ$qzwg5y(UA&YSTQD%y17I!p%=ZkSE!v3G&N9@Zr_9tX
+z)2-e1$&_lR_(u;;?Ct-{`}&WZy?-p}?@Ipb`<Erz8#p?dIQ~c6-anS~e~sJw$2@ya
+zQkf=yXBzoCF#fqU|NlJyd+Of5*`?&Deh5E)Xy8Ap2zO+LaE3sx_ySI$dn6gcbzUOe
+zct2m)H_%whh|E+K@dH_^3ZamBz$q}1_KON@=XX(`S~ofOp%I{RRsAO5P$N|24K!!W
+zSKRR%<dQG$BGI0NK6aa1jjq4`$t(Cr-B=Jc<Ga8B05gBN1^=^d14lC#8x!0A(%t==
+zx}#MU?Ec=$e}T5PbAuF64U1N`T3S@=Yt}sUU(9{D0fGf28-_xOxDvw*0bj4%@rWcO
+zvTS_nNp4>^H=VI!D+wA<YNo5#?T+O;UdPRos%U<_#1dbRke1!MOv}{8^4RKjUhT=&
+zYT9mz%l;TGV8bumRmHofiH=pS1)!VPY5vu$@+!tgtF5<Z?>|y1D0iK0XqI4D*Bd@P
+z6#&~Jc|Hg}D5yalW{S+iPQ9+qUlQtI9#A{*!h!t})q5i!@Y<2&LWm$<LvmmK*Ki4^
+zefSpb+{6f$b%8uw{UJhwtz!Z}V*dS%x9G;VH5ImKldkcDhV8^jASQoZsbtGcqaIG2
+z_v%CfnmGDj{YX==%t1h&^Vd+qLXD(9$cuniF@p0y!PUud*njp2QB61Q0p^dy;}_Al
+zp2wRt=dpGj9+?y((y*#cfRCS6AHo*F@|h2;ivH#d_PO{exJtj+vS#PHgfN{7MG!L(
+zvl?=AZ%XV`8V?BO_K6SKd5Z#i&teL9nF%&TAMGeihV~7i8ePKFM{qgJqL{Nx=A%)?
+zshh==lNPS>-AyN+y&qKcS0r$AP71kHQYPJNAi!-vk~JAR4t;bUqx%DHE3v7p4U7zR
+z1qe<NMR21u>B=nq#7jtd;$N3f5T*rthZ+i@S4AT?+dmwMu<ycs-1!npC#y~hz!$>N
+zf91-e$4~@~naxdcD9yaSt0-%3S)>}1%9EGb6UA-uPGN7I<6BN(wv_Q7X_L#nl4Di5
+zhAz{$tbm^0Is}dPA#g-o?>kyUG>^~*;=?r3v<_D}5%|id2tuf(lpi~0ev+^FS_}`)
+z)htrKVmaAlkApe9pep~+WB(SX-r7i|h&6`0jVI`3#BwLRSM0@i;Js9T+T6scB6v@x
+zsDK~;E4c(&r~RVjYl>8W-R*BKyR`?qBTV<lAeA3oGUP=sl!@3|r-pZq|1cLv2+FAL
+zfr!&CcasK2Xn`ggg2m@3{0Suk+D9Hg3q_~nW+y%2ysEQsyG*a^i?4?l%i+-yW9c_}
+zN|Qr(r5=pY428Roe#_Z}op$6wcIB0M9_H|vA{(JRe0pPK!-R|_9kjQFPxeD~@M2IP
+zm8?NK3Gxm`AsI+8)~xFzk<C_+)doC2%q*p*sEfT$mrBG=IH*LW%&yImw`t>3Px#$t
+z4-#vai|gy<@pXRgJ(R`)>72@<gE@gH2CSCyE=8obkSZ|4N5V(U&<t^?i=24XNMRC!
+zH)Fq2>hBejUU!fhI1gsKMNv~)ms2Z4T4P)Hz6AYA_)H!qW6fb@Xl9FmW>i@0g!1X6
+zG8~~qHp(n?IQj_9g$lYc-=FIrDkyGXkL2#mzyldVTUzdk=OBe@c8L3sTM1h~O5^r<
+zGFe##J>V9-f^c+wp@KYm(+1;tuf^`8NA!Ei|Ln{i=7!Jz!-fyMEUNXFV2LVx5c4%q
+zcddPe#tk-`+1Ii1YPeFY--@>_dwQXGmDX-rjNA}M1~4*TPDak^mP(rz448*{OVQbx
+zs+Y?**_=B^{F+(Jt6xP1o6r)tWMxH2DyZBYrX5&qRc3X#i^TV7RfQvW07<Ly%^;pF
+zF_I0gmO^V1_Uo?^{t-_MO{)%Zmeb{rg6PjvFV2}_q-ATK^!%EHYc`m`P!w)s?=7@H
+z|J#WvY4*B7{I_*!CjtOK{~ss4k)7?o3YGuOftjrNA8f1}J%36jlw)!0h0ha?)>Mhw
+z?5U#+*@R2)JGZ|fk;FsD`S5u(Y>9t-W@&Z7B^^&_^{f;7ZVdHuyiuceu4OKRTq|5g
+zmR=4Fw==TICq0*yOK7ZI=BO6IuUEUDeMeLpww<|_+ulA@-aZcw3KXsOHP_cPIazKq
+zB%57LG)*E!Z<dZO1o!kRwO3gKa*~=|s&~*1-`x~j-fu1SCX);JN({NN<N~aj^i~}c
+zr3&?ipIe(A7`suMEx&*qylHelw$pj$2y+_SBKsGA7&7%8j@~6%lWX#-{g@bqsHyuC
+z6MRogXNN|A_jYw6Za}uGR3k<s5OvnFY^|~rZ3QAs+Paixq1?Uos3hwO+=0uvTsuE9
+zMb>MTk({fxAK<T)pn!pcOAhYCn%v=k9mAc^>SmfgR(FG;i<|({Bz}E4Ypb9L@S%q8
+zD%&KZakslWT<Qqrr~egu9l&cdVn)B6b`~eX?`kyPSo)(BtgK8=d4S@nw}d^NnG)&t
+zdJj5UbHv?Ad-?Hvnq$B^IZgUvpJ@-qVUrq6YNfF?-qfq=7P<K$oai?H!0#tpBxMVN
+z4vST>G{iv~kxg-iX{2xKwrnU0s2jvTKNq^KQAU*R0`EUb9+))xk1eaVI0j!B!i66Q
+zblSkLq?v}O+ibbMFsg&RJ3k;?;)Z8#aO;~bUZ+%a%k+~U`@~*Q>z0^*!hec)g_-^6
+z;3RyX(vU?`$dQxXea=*T;NpgbCaV%qs<NnZdDlwbJ4CwioOQq+B*i`ZP^C@~C|H3n
+zDa2O<ImeY7U1*W9RdDn6=N}=VCYnFib`S_CB?zzJY~p7L${R<cUl(cFsIWw1I#n)y
+zP*{dOV8n-5t$hZlEHJ*!5HOBkUp@OF_=p83VaC2gn2>@bMjXx&iE6%mW$29p)l;3>
+z*>Ghg=h!>HX}CFFMUz{}ySxE1i!dt`B-b_XSmSVa1C>Eg>tTWiv+-WFc5vuFV~#CU
+z{Z*d-s#h)$vY~q}VZymMOMino-lo(b0#W^)gibCL_PI5#S<AwFRlI3<T#>R8!AE>$
+zp_e&AkP6xORit`TmS+LMVRwuM;d-xQglnl{gKV|8^pkT?uJu4fsHS^&va4LEIfHG^
+zL=~M;-31KGo9(chAwv~ENE0z3;fdDrV^+DBRntGQ;+#pdbF;otJ}W;HUtLb6dBn^)
+z*j>f-W)j^x7eG(j{!2Y$mlGIsp#EK(ZDaZrMAC@IB&+KOhn^wWD;l|Bb=X*vU9MO9
+zqbZE>Y}e{-GhbP_5O>Is;qKh9pOK2YG&gd13D374`~fY}UiSkHO#vdadl7W_*R{-F
+z9o>GJl;R;(2BLi+q`Bn%NHh(BZE#7tF$V}4j5Pvp4*;FppnH4L^P0p?f&t6EPU4U<
+zBudZ)K{TA!(wF5XOO|@adf}66Qf*!2Je-WIM`1_?n3yz}8Fn%GNFi=+lfedIDsUq_
+z@p=R?1%OsMW749b73M_I2?J<cEYQqdS+{@RP6?(6S7KAzPDYZZZ`;(B@3`0wKTVKg
+z@+6X(fT6u~t4H&*_r8-@FqxJ5jyRl!D7?o>3mxi_+m-34&{%RFFtv%w^gWpp47+$u
+zdA)pyW!4Fuge~RlN#5?Z<1HLE?Vg-RCmV43(W$#S`iG_FKGeGeAYjjl!+L7%QV{fl
+z?z=GLCvbf3{z~5#;^Bo1tUyQ1zGQ7IPLl4ECzF|uF-r6hI7l{%buIZLrbE_LM1G8Q
+zD0xrU8z)9G?k40x`7v2xCgCp6`8G<7R@6``+HSL6Z`W7G3+Zp-pmga3oUYhSaZ_V(
+zRfdj-g^+?X8&ZEXJSp&O)2H9D0enFfL+SeKauT|A5x^%mz<hj%vtt-Km%C3~hU(k}
+zx(VE0ZC#%F<EI2qDk|#$*pVXGjPd{=EGCQmQmu}>1l*y*7_wz`Vg5rK(-SDdq|=m1
+z0=e_(<ge|*l@aj^j~q&c`AE!uk%4aPevs%O$AXBi>5$)&QJ4=ZFkkBVV2Ao0z;=WZ
+zJ05aP;T$R*AZrDW<v^5jDjK#DQbTJgKrq@WRRKPg&)FY%Nkt0eXtRT&gsV#V6EYAW
+zh0P3wejU_mNOe0_7|FZ(00SBKG|xp_O#=Ig;d!YYpeg}5#_UM-xFrbr9d^A02Lf|p
+zVbLP&)X(uGBZ?qZa9!E)$nM0Q8}R|LHltI@n_Gd%EGJF*jZWMGL$?POW{o3LuCc^P
+ze#p_mk1R8Wj7Tq)MIHt(eeEKOXpv29C-d%mB)~;cbH3%+s$tDeJ)Q@$A1oQj>Wpnz
+zz5V1YG7V&6b3j{%=LxFCY|1OAD<TBL4%H^2Kb`YsfKhy`OGpJrHRggQQyTfu%wooV
+zy<amxb-7NV7Y;LRg#NSkAm5uBjE+zB%RN`}zDpa6%K@ii&^B2@O9mz|vMLJK`fg3T
+z(QX+Z)N;m$=-}JA$Q`~d)fQ0Xll#^QpL=S!Z)=96Qsjg4us-PTX3l%wtBUoGx{TbX
+zGMOtULVSio`@XLXJe)S8Y)$7Yx0pxJ;6}S~;dTJ@01DHPLsL!31rU*>r??3ZPlUnW
+zFHGI{W$U95L0Tqup!#fn<F5sPHut?mqJSLcmlzTDNVleE_KAm!*~GHQUNymuHb-Z3
+z-dXLY`K40dGX8p8ZCD2~K#?)A;<Q<f5=lPVL`mk(NCS$oJee!Ln+N?1OeLPj@*Kpn
+z@(Yx8L##m8d2g^wNdSTP@eF~uqSU~)NT{$j9;7>Q_M>?$q~AuqV*R5egs9^bjYc_m
+zlokwXV$i&ZW?$MpcF(dcv4fN(OrKP(=}b(B$C+_-9ZV0Zl{Lc(lLU^yu|rth@I20|
+z80r#+#A5T(0_f~dq-d_05|8tH+kg6!!sEAiNdNv6U7iUwc>o^mr&qw1+{J?j#)SLK
+zY<60HDN?jJ#m$IiImS6Q615m%73qsP^n<$byL;b91!C)PNy9`^HkLl?ZV<=41miwo
+zZ&{2ntFV6gJe)VMkI<w!hSt${q)YY|FW((<K$SLVNupu#X=aob_fQ2iKSpuVb?9;W
+z=<KG`n`QXvS^7$kECdbjW<ziW`guzptv}pMMs^a+=U4)^mDEUph<R-__$Bg`a<^DW
+zmysP1O4-p0qcj6o3l8k#FBa>9XpQC!=qUtgJN$?Q5Qt)`Q}-b;lGR$V!)K#=)A_2q
+z7|XPJZPgQ`L9M<sb+c?akzne>U&4{VjkVrv#v&J=ZtpE+KtCzR!RxLJkve+LJy=)P
+zQmlIG$<dIOA|<J%^ZuQq`~3cm^L%rcfI(xLmCAgWW%Wijje9|;80TkkV@9|(Obi4!
+zo7_M;(mx*aBh_{;KeQ&W4MEs`vVEv~A57whU;SDe*DK`gc%6*y`}X!TecKyddcM5s
+z80Z`Z&bLQw&AV}1(%YVo)#?5<6gPe5ZX|P9Fne043_)OoB?qz2bnkyN<XIZIuKD$T
+zalNEx@CbwnTyuXlW!jTAX<KS(RYJTEI%(H#b$^4k;p5rB_sQ<`0CBjqbac)k*T-fK
+z+gi?b2gUc9V6{k-AnE*L;VwTN=)GgpRWLqYqO{{JyYw7hrX<l1*xq~!f(OOtWZ#8B
+z$!<rwtRTITdr;+Kx@=iV&#e#<?CL@57<h%nyiaD&`2*$c!<@ODH@tgNQ=rDir@Jjn
+zE9-iKiw?8%tHNT%$#gK+6!iAvA-pQJnGdbh_E1IlM>%RBIpz=Kid4GdV7KUkslMRb
+z1)C!EIb8=bcef)1WQoHd!U~gzZz6QpEZ_9=YW@fmysqDER^D$e5xj&2esd<=JAc08
+z`Q~3iFtls~I6RkkY7L*&*7$kPc-{@g9JWPp*3g(jxZdlTd$pZ(=d62-S`+LJ>uFMd
+zylKaua&sPz4r5v0y@3zmK*yYW8$mYL{Ey5ngR2Ib+Se##zNs@}*I+_8`<;KA(OXm>
+zci-R8oDsqKhEDYQ$OwPjvA0<+^xvhFr<iGC<K*lXOxA|H^JLcB<DXvrrD`&IOu>yD
+zse3jdlR|6lN3j_efQk5~WIs=xkW>q&*oMZc$-x82`4gUh`3LuW9Kv8*pWo=QVv*ED
+zSP!c1gl4)pDiZGsC+Ut@WQLqm>)x%BC8)Y|nxt2<8p`1nKhbynRDw{1eSs&w8D-_e
+zb3zj{V#VfyFkR2C7p^WefWt!gRE)TO1^1-eu>Zl)qZB3lYJF%+NME`>>OpU-Pw6xv
+z879!)CHsXY7AJIWM^F-|b1&pMt4iqMQ5BB2>e)4}LD*}e|Bh<!HCA)RSarYZ{3G3x
+zi%g@pDpRfoT!g6)b{41mNtnhs_k@*R!U=|_suYp+M|G49UCv?Z)p9Z3qB}0<AV?1C
+zPj4uq9;cz`X#5!&TS_KUAKN~a;|Yq?E?V`5)I=RLB-r(_G#m8QBYQYIsK`ezI|-r+
+z8_Bm?rhAa9t?Coae(!(Bsd7oaZP5PuLP@g#0HFPkuIoQww*LnmtXNCyuLK6w=e3sq
+z>RfU{Q94oV_mcI33>}k9>tfMn_PvMk-=1p(c^sg~#-`+t_bdniV1nY7DqM@mFEFsb
+zd@vyP-PL5<p^geTGczomYmtnda>hz$&9YE0on*pO0=|dUojjTO6OA40t+L<I)=OIL
+zJIBS6LH%~&=1tW9i>-5N&jebwb)0mpj&0lO*tYHDi*4JsZQHi(q+@h!ot%ra&tA{7
+ze!{$%b5_-;F<zOSLxxQm+srOIN2X@W)_=hs>XlfbSkG@}<<TC6&c(AcGfd4$?HV@F
+z?b$oy1uBh`%)t278LFuPc*S*9HdEd^mnDg>#>ZyARoXv^l`Q_Pcf^e_pnGUm^OZRp
+zndRi>E?(O<1$akfg!s`&HO^G7gjo{X*z8#q;$b;D5*w$7N3fB@xFx0%plI~LaFg*8
+zJIv!WR&EfXP=hy$>`>6E1shK6-cVp9rS`eDtBP)2-#yB1ornM{g=Hux*1A#!s#TyC
+zceqxDEB-SGS&}Ss&}qJrAFQidc5nZk(3%+W%x#jEpfl??pnr<T0?T4TEMFjcc_d(6
+zQBd|zq*3WzSbR?u82`nybT1uk6K5*K{5DH;hYGFh*7Le3AyZb9f~%8y9!tNm9E8me
+zOp9>r$V<m!*kgbF7?U0%3j`&AB?|Nw=1SqTMOwJWUj*LU-pLvumDg!Kx!KS`@6X-a
+zVL(YB^c!ZqJT?sAr)>I|pdwsdg@vdVtz<Xey-}NlmnKD}i?5#?Z5;{05_{0r{;G;5
+zy1hlhAd!+sUJxowW3;`YiJ76!r2-wyN75Bod;qC^(`^$^fOLmV4ny%SJS>aGk6C`}
+zC%vSyji&z*GBjQzTqItJV$2DU()=TYV2+htt7(Vk#K>(%@3<9l)KG4t^6MCp3*+tD
+zAv~pCTQ-`CEqwRHkPCFXewxOog5i9qLNjixYP3NJHFIeO5r&=J=c>5P&fb>m0x1kl
+zfNLJ~kDHPOeNUH?+$j7t0@2>Xhe}F54xjhyyF2v#Q$za3ZKDPYp7n3QSTLN^VH&|O
+zYkDBZceF>|DJL)F?bP<jC;W4@!0--b4CI-67wY9s9L|iMh(EC>1Rwc4${+7n;qRCg
+zN|>;~b>P<ZO9!LB`-%5_7lfM}90@PPPyIBHB*IG`h7nMD!JxGULQkG7pv@xh&`!yB
+zqGB9_ar+RyrYNy%Q1Rzi`gJg-A)|tZ)oAlivt6xmm#K=j*HyiHx&9~|vjYglY)-B?
+z%<R+ob-EPLdk8!jxq<k(ub91Yxq&0@!t~hoF+)*v1n_GKnQB~tU_idO1k~ogUm!!o
+zm@to03+4j28{_wnkAI*Go+=@SlmSi75cA~shaBl)zctAG&|#x-z9^r;Jl#yBrCt$4
+zmpV2q+sOwZ^iHMSi}|4;3{`cAyIUlNT~(5GbaO;38}<lf7B#TMzn}@gOba<l3M7cg
+zqFGvBJo!d@PtP8!3LiB2a_n?>{6o^z>%d^Ua-yl+XVYLWbIdiv*qq~;VIu_WqOIw}
+zaXDiehz0764h6m52Yq*X+EIJ22zb&Q1k2Tpm{!_qL;48a@v)NJ!^=Q*m8=C%QRFqt
+zA&`^WAlv#W(T!xNc9y{p#gpJa2r>l&Co0hP7I+d(%~0Uf4fv=0B>XWVmi{#6zu*>t
+zNjmV~CKzQQOuW$i;%S}_)#WJ#M`z(2X2qw;a02D{1zE4}<&!U|X$?{JrR>a&7#Xat
+zqMxf3Oi_5+fX;gqTuT=|)$EHdeFFUOb@z7udc;b@-^*WQ{BLhDQ(tf=&xf~|$BjK>
+z+~?NToirg=$Ktm6<$(wFgx~;4Z#-<^24xKQD!3?f%?;c@IC*aw%WwcygfX(c6LD9_
+z*#AaVkc_2SJJ6zxXcx3*CT?Sggs-(lGteFta!v*AHpDt#3>Fkd3<p}hM8Gd26;_N_
+z8pLYZUFj?^?U)kCvM48&SMg-UY^L3`!3~Kjk0=zw1k2&Jfg|bw=wkD%dn$gG^fG87
+z%VadOGF@Dj`F7WT%3fYPa=&oYW-v<i%nK$&7z~o0eke1RcCV)0KMrvML&%<JNp<QZ
+zvqH0~2tv@Xr@wdP?!uTc|In{8%1p&0In2b6)j;EXU;@<~wvoywtJdh00Q?V)`1XcD
+z2J)8wEk!1c#MxrxoSQ+V$)iHdf~%n<PNKF;40GTybUny`H#2D=nlJk7b`lb>QO|TM
+z?MixO1yvqSpQZx4?#LP2@B3KS!16p;w#+i3mJomJp)#XhA6nfgDepzJ;e_lN=#TR|
+z4<`}~#lIhmaNhRr*=-v1Bc=X}1gDoyi)s>iNZJDxM?pFM1xlKu-D+?P90i#8tXWy(
+z*J>ioJ-=N;iup>-d|P$EZqcAOiJPP=e~06<2Sgd=9N-e(+z@0!1(f_-KKzGKt%)_n
+zNs{yn;umTXwS@Gd4O=3^N1Ox!1(#rqAJ~wUP!I~gU>$%&0-O%5aWjHQGgtzX5mwZd
+znU0l)a;%9zS_wZ#$IZSjIqI?OKF{G37Jf`BCRPs3(2(6@s9nGb7X~Tc?u`22l51Et
+zXy1*~=H{jTwt(7AT|)VQ(N)*ecQ7J5Y=D9&lK@2ItUr?DTLkT;-OYhSdhXC(Ea3e4
+zC1uPR4J3|AIU8kkelUvd(GP17=`S5tSdrr*uW@u%w}F;&<s9JK8fsV$RaEYU$_EOK
+z#d3CQmvTx^DZ^14{)LlBljJx3F-`b~(7O7%*v1*eUeh|bH|S=(#)E_PwKvv4Q+Qet
+ze&0#Smw|Z9TS|)fQmNA^-{r{0v{sbuHk4`|8u$J{AfDW(kT~%gyry<PN*_^$f$u=a
+z;2%39z`g+`$H~V%s&e}0{5nhb1)J*_a^~cuNLwwPhm#51n&dhSSyh|FGtPwdaY)V-
+zU!!EJcy65nZtJ#GyC~HS8uZ2^{KCQ*#vy^v8z&uJyPENMrR5m%!3^oa3?BV$Qr>kP
+zMD7|9gbAs`wLgd8H5|?yM9iEvAf1&4vE#L0vfF1b&*kbFIIx+12!V&|TQ61PTmC0e
+zF0wpX8Gk{#9P8VT^|7jV)n+~_6Fr5$w+l9W)&n4xDPKR2*?ZF;O!5Zfeep~aSYtX>
+z@5!kDx|}@)!)1R{U5zpovQ3>-uP#+sKukz6)_$}gZv+L>q+~Q|p+(ILcSS__&#Zj4
+ztvSGVsUx6%x_@)X4Oo~5;=I+LxrUqjOi&)*7bPGFrLd{1)d+}4I0CwAwFJIJN0tC|
+z_hqM_zQ)HR;6x(7k5=;P24B?e49L{`mpt-e>~t!tA!mEVM#up_e_>73xZ1G646C*7
+z3d2bL+-cSrt&mX1*RLp(#IU^$B0sA_xWH!>-dZ+C>XdY-gN39gUhWQi`cFGqdU97w
+z1W(1hYQo)>a-<TsC9EEUEsl<G*Sj2~eG3_9*!M8}Bz>JAI=opWUF0c^oa5=3w@Dtd
+zm<14oSV5Gc5LK`+_WZa`+Gx-7qc+9$NY8_I#6N0TZ#Qm;ktvkTFu>?>fZf+L*3y=L
+z9;JD@lK}Y+wXK?8W697}ngx(RtSOH%@Yt-%Lt5&;4vQi{!P1chDfTojDTp&#f$&2n
+z$?~545me8yHX*f{BsQ_25fm3@EqD}Jm@HD7cR8N|jmod!$if^qe<0K<m9YSLO2MW2
+zDIh(GnwngM3Ntnw0(P`>ojI<95XL~E<<BKtq0vr!R$Ip*y2}f~S3S=&4|ULWv@!7o
+z{1vE`z@B&vE?}IIw?;_)U--2FG;(iACwlF~obhIo4#8K@ljP(QQRq%7KM=!3<@a7>
+zN#bA61LbmhxC}fA19U^&o-;F$S2eNXpPxPjo~swyYhN?tK)?O)c8jFP*BgLaKNa&4
+zplGmrEz;z?mUIUhQG}SIW|=&9A~Pa#oWu#5R@g}&gNzmSBr*c8K=qD<G`Zp%%ryKN
+zzn9AlHUTJ?AYn8ox^=2n!Hn>njYuRH_9CGUC9Wgb7;bY;*kja)11B!}naBAnVuk^%
+zkXGPGQSj2OdZrpMWHBDCU@?RrFZ${5Nsx1|0B18#lfNxXiGDClvOsw^O7h&rBb)PQ
+zQJ}KZrAVW{hWrlZZ4hN(s=ZGm!N@&g+z550x(&J{UelNyTqA%(!WrEv3F^SwutxBK
+z@8^|lmAx5!4VUa0u1DTj1pjUa7u#GH`aD&O>Dy#(lU)}R791Y7&5nAaH!!FobJQGn
+zL0g<q)4|>3h$g4XxU%5W&<V9w{dE3%pgZ+fQ!3c90oDqJ@LQ@Mb30l>Dy&Yt@It<G
+zsJ(eM9heY|yS2ReK|7wU-3kOjeXDxJ7jd;fqH2Bb%xiSlr__04ywW!sU@fu!LQP*|
+z%P3>Mt?tq4i4<4~(17$=YpXC7$=z}z5t2GX1A2!9L_*I*O6wM&BAtm(s@)d%pQw4a
+zR-`}sw0_r`<^9Di$hNT1e-?GMU<f)dbW*b~U(Ah?XtHJR{eyqW%Bn#>*Wi)pCQc&r
+zoCzKEV>IE`kz7K|nE-V`Vqj`@olD(LG)}BST4z@fy$cLx<1rJ7)t|(*VDcBht$fRb
+zm&%j;0q2zB1L+TuA&BWqM()fG?`20uY~WlPWpr&ekTFNL0_kP;sB!4RWNeGQ(XA(R
+z7Mn>ufbpj5!Ej78(8cXQnt;|hQ&@OGzjMmP?wXX+=G&eeTuk#+Kj*Y`Ll~RWsnw?J
+zfQRnN^xF_U?hl6Xh!jIy?T^!&ghMrpVuO0WS?AJ8^T3qDm3od9;vXDnF00TIvX5A{
+z*uW~e0Vq-Ju8o{A4NdAlJ>;&${VM3!7nyh5ZUV}lL$KavFa0{**y#Wd_eYq^0H)Q{
+z(Hy+{O+gkcif`>yFoBYUz7!KrbAY`t=)Eva<8U4E?}*9OE$^9z1`s2@u_0e88W|;<
+z5=uFp9zAmS4=j2La<+~VWWB1fABOL5589}4kHV@BBEn$f<~!8Q{Ty+<q03d&mx@Bz
+zL4)w$<741$_)EiM=CA|KktV`SUX1m~#iWu!5md-+vo*IeRs0X?%`0B``kI>)R*i(K
+zEBR@pdY~QQ)WbGHjtyBnA|Wy_oW+=?I9Z~&o71f@N<CYRzsXO$Npqa8sy8<5yC<2C
+zgKrczk${VsntO+Q{ss)I!0zO?6lA6p1z@Z=w?QLg`Xt%W4xFLLPJ57EQ0{x&%3CiU
+zOXSjnr+M7YyY+z-eeURCmhQkLH?6=*ib8Cdz1zBb#1<wH>CMRt$dY^GC1s-a$2ZZx
+zASCQdn*t~-Io1$x_-<*U3=V4|XCV*I|M;lIZ(4B|yIhY^0_5B#7?3j)!uEImq^>bz
+zTgN?o^&X13v6Oc8x$0p?VOTqXU;?h6XPc>@%-*G*{^kDE!j{>#cdTTEU=EYhGOlAd
+zx)X9+&CEvMN&y{AUP7x7CIzmJ%(;uc**8ZTHkWhDQpR7@6FqwyVl`jn+sy_Aru6Ct
+z&q-3@Ek!8gGwm&nDC0(lFV~c=%(;Bs-K7&8cCG(-!by2JWY1@>p3^%1z5v}VaPKY?
+zz1;mT$64l2&V~#29(g?!5YQaTe>l$UtPPw@Z5;srZ^Yp8-%~cS)i)hB$B?{F)E*`f
+zz)+y5-S6v)ldOo97BW<H6_LspxaiPXz(T-?fC2|#h3$KN<Yr;}A`gp@Q>+h|78g?&
+zw@cj}9GKc@gEY!Zc$S-`?^JT+3lt?pzGzrY8sg{m>&6^pKFnCkWkiZ4Hw6<ENK!Uf
+zi2m<Zh*YLD#zt9-xCG|q_Mzs2qGCp;ihDC2s%18LX{8{5qBhDpqlK-#!ij~$pDj?O
+zP}1CwoxWr&H7T{(Qu?_FHCcVqbT8LZV{GWbRf`v+t8n<z(gpZ;E-8E++J|7Iv`uJq
+zQ4s)3Bi&HS*{nrfO)`@wykHC$ubGU#!;v}vXRtRW8EogrV9JdFi$}Kj69b*aC-28x
+zD2wX-!apdZ`}yF*(J;beoY2i*kuQlk;9Sb@%M;#_QZRoh#c@tptJpkW<wh!PmHkHr
+zqQ-PKrqwD8-?C#nLs|s~iKF>9_@x9ixSr6gOLl!-qG-fH(7)2eT_mwiUiLs`2^}~%
+z6~z)%{@UoWDFou3Im7p>C;#QrMWuxZwghGHc~MItd9*bCE@1(ed48$Im5cy0g(DHd
+zE=58eqM2C-8?dDmGE8^G_0wbOyO~Z5M82KLVjo#7qujQaOaNT<=VrUbMo>1AB62I&
+zU}M}~SQCX{N488!<V9y(%BE;djEJzh3&F+W(Zl(@v-qtWTW9CM^GECCZ0_IAi(5ki
+z7+QoSO;W#!FHivMx-Up*Qu_jAERCpQd%=daQT8`sU_8ns-QG2|MXL%o2P&1no)zt;
+za=UAr%)O%O#!<+&>wMplOmQ~Lm-fP6Bl%{NtTMsSAvL%gW)CltMuk!+#08dQE{i+T
+zgjgGNy)f2@yg|vPV1vEK1%iu{#<-pD=x1VO0BH){^eQrDj8h=jM^QYMC*`(k!4%4f
+zo|AswVhF21%n&zwo%~D?o&VZjRANz2t13Uf$YGR=YZVbB^`naxgKD4pi!31us2Aax
+z#@ty^LhvW!t^R}`XV^!#{?z7{Iy2@zsU$D~YwpL6Ss5S8749?=!%$70nL<;;<d&$R
+zP(anjN1Kvccvq0Ce4QJ0?hLC@1r>k#_jQza92*j+sqrn`>V=n%0M4qoGS5{MbhXuX
+zi;4vS;N@&_P70IwDBiJvNMk?~`YSFv;VdlW$}*T~2;!KE5xi160&5+hEUYKr_PYE6
+zNevlExI)NVA##Vhmlx+3*(kfsXk9RD%=Y!$KkK^^bZi8T-mFAZK)6El@*$6OSrA+q
+zI^Ba&me$iK;|hKWPX}N7OGMQ~%bYu$@2nsjnh*y25qW3cd$__ih5i*QM_K1cIvB|q
+zW^gCx%-Y`DUopI4qvOhI6HMzW@8iI}+Zt|FG?rKSkr&hbF-m{j+)Nt3Ia@8;=|s)!
+z(tOq}^i&7hS-01JwVLq9-^a14(YhrV-#OA~?f@RFMntb^<0dGjN53<#XMQ50p8_LT
+zUP0bo9W23=9#XVPLTvDkpypi#<zJcvbVaU<*RK1L(k0Dv4?SO9Z*yRLt(C-$#H)N`
+zkit7>cw3BqJZQ=OxbUPOl1*VmOMqv*#pX|0EomW7#@g{*hHl&rX&2^5+YCf60CTr?
+zt(oUl4nxLOMdoT6+mWT`A#r$URuwKL9B?T)uL10mxNdSs8s7|zVpk8IuOK51qkplX
+zBWwC~(6G3>6YJtv8@%#<6vbTjlm0zCe<cBJop)=^mFwLTbMkWsE-XH7zmAXJ=yIfO
+zO$BxtpW%U{<3%cr`fjYg<NowBY)=t-uQ?AZyqtfw0h%mretSWO`uHA|v??IDZoe%v
+zZF3Yb^O*P9X1!;+jZd~XRU#x`uKGAm-~L1ZlYN3Up=q1o{t3%54pjVHd^i7gZuNn+
+zE)$$d6rDIPr2U;}S-i9JnLS)P69Yt2Llbu}HsZ@ki#)xdko4K-#525@x%b}h5_2Ov
+z78e!QSSx4e;lp>((%cqd_8htSE^--UgsaQRe`XT1`R!Mx5GqIh$=w3x)l+H+40H4}
+zi9P>Z?+Pg@dJiLfn^7ht<dj;=EL18c_h&LWzjxua|M;qM8b6tF&wI2Jy@GG8E7g!b
+zALVGFjojC>5?=FB|0cGOG6;ob988X_AzY_6o>C2ja)LO+zA!`BBWIPlYgDWi%?Spz
+z0R8s{vihNy3;D8AA0~<eYA=!N)Zd;7J=s)>JIYl<VyREprbnL`1|K_=IJb&(A*x2=
+zzuQ3vWLZL>FO5eFw;`Fz@go?ZZE_4=ojumbbHFt#*+o8}MPh4lG`BBjkDK0Tc5ZaO
+zs+Wxi=HvgQz3q<iq%dabYyZ)aFW&I)#$XuQcSH;L8TJfh64air42j)p-5@@^obE7o
+ztDb`5M(@4|$f;Xw-O7_H2=gb-DWI%}UKfalNjWIenGft9qLMX#u!m?sw|VEFy(Z$|
+zF8XBknrohowf}(uPZ<UHN{5l|{mNK<KzU*G@4gHf#0wqM%;6y9e@}lKEj3BOZdnjl
+zpWncX2w&8HYr->{I6Y!)t02t-P=fTriN^b~UnBv=&UMfV$ejtp@V^Cl2yri7B~9S2
+z+*x3lk#m}pbv1oDstTH29`TD_Kq=1O-?%Xu(XcygqYW&`oT=O<j@sn>fxXvVXpD@#
+z`@4+AUMIRD(Zhy^ju;=%JN#m_W9E(3Ub|Px{i-Fd_u1Qcdifhvn@R;tHOfT)>HK1y
+z{hc{#aSPO~U8FTSy5YqeyX)K9r18qm1<H_?)I|TP%tDJCI(3r{ga^YNRTPO^UiZ>E
+zt11lwhM{sh&wTzs5ajs^*Tntz0GKHeK-+`ShNl{i0%=m~)^7e*d%I>eCNyDImaA2B
+zLyv4nyyBa<lmVUR?y&1n&$5gA)tc{y!+A?tlil)Hph6f-wK-Iudlu}sSXnlHT~FY?
+zewzRJB?7<fNKMtYwCQ5!#bjliY~H>(m^JJd*#8$<3V({9HHHy1SWF-wd*%OtZ`e85
+znmHK!PwnIX-)o;{?ahQuj)Wg;PTo;i%3;b{&35bg{FH}xx_#89%L~)zUUeJ+(vRs$
+z9FAV4`0A$DHfJciXkz)o-Z`{V7${trDBhhZeyN2D!LWU+MMkUqNYOadNQ1S2wm~Mn
+z!X$^aBEg*(Wt55<>s-w`qlyNPhXJ>-fbVCi{ZC5sXC+_zA8hB%E#<qHy?(St($xy_
+z$q88%v(^>^CA^q+%OpTUu8w2<v`fn^r7x3=JEhW*c+Z%F$UXb{O+;>{teD1f&!W~B
+z3v+Kc)>>9L`Em_!SplQbQuRht^vP5E>tzP}Ih6lW%iy7ZUT$n~N3gc?+XowQh1?{M
+z8cqAE$$f!!<dXD9SB0cufG2MnRs|sOC`Gqo<;sRknQ)c!O00d;1l+r(9H224SaEgW
+zux>qhu0M*Kx!5rCrN--aqn|k0fo5Cjpf)~Or2U=9k*>>>TR0!)`%MMYuGDNk@s~$!
+zl?q)>ue)fvn~oh>dsf(y%M?5Pgxo$`qF!^k!XkG3wn-DoR>qktr3l~o&TpJo$+?AE
+znJSDp-qcyi#7a}S9)&*q(2NvI!?)zBS+G2~N@RPCDA`?uLXqvbIfs!Ii%J|)YJNpH
+zp+rHYKZ}J_HIw=>)%+=9Z6b}2(L_9~s$8Xq)r&N|EE%L)s@vy7Th-FDy+T<FCD2i0
+z_Q)&ycLP^-nl~kuwHw)euC4J``?{J*=VqjX6tih_p`lzz5wW8r(SC`glvVLx(n=j(
+zU~Z*9omWA*t|W^U{J!hQaIr|0Lzah<r`3gn;tL5x<n3gEPIz3jH4^X@A#TP##BwZ2
+zY^{jwo*zPDcj6!>h88W`70|ek8@Sby_YdQ|eX*o}!gCHnRU8;GHw(FQWX!r{e;ATF
+zRg+BD?CH()x^raZKKHsuqr<1P)<|b_9yTR}?U8U6aw!ubO&FUklXdcZcTFLIMU*PZ
+zQKlqW1s|zV6t3zYZ|5)aF=mEYF};I#Irkc?<a$W}M{8`uk4=uA^09>3*wSg&b;QBj
+z5=CoTJB~3H>mjs|mrQrHOz&R?mDfbpM08EbrH%Y=P6WIa!<qp5w~7{uF)FWn<fe-O
+zq86dZxvDO!NHXp?jCMyY3k|`LIw#|go^-e=P_ic>=_D~gFB*QuQ&ro}^&~bR?KBEk
+zh1E<dhE2eMSirQXa(B!)1wIpQ(BCpExGTlIG7r!smw48O=27?JB~<d1Squ&eV{x=A
+zbgheIPgrY`azF2nabcT`^Xhfu`GNQ0L&8nvPt|lkM-GA4UGlVrn`Mjk)?8t!C{*<q
+zf7b}Ns?RjRH*+GP<M(~Tn8pYDZs66fc?jm5OQA7z^lrj21M=Zj7`s3A^;&o(Bz!9W
+z^%n(Qf0}zSlwHE`2Ns{bFyG~GWvG-uFjC`;1@i{0K7h4+A9%HV)LK#BU4<nHJZebT
+zFK34!8tj`p;JrnDTdLzcw*?g31_+*pum>KJzKU(2J}n0gKwSWdzWmfOFjtS}?F!xz
+zL{YIuep=KD*tR3lSq5w9q9+ie3Tu$y%b2`*8~`p0BFvcsI|2KGG<rG$#1N%#UNfl)
+z^{r6TOh!aLTIOa8UmP;W0ustHw$*_*$A>$oQ5~16hx2-9=YB&LxXlbpoh+cqme)dr
+zlf2s^b{A5eZ_?5%8hkd<%)_WB`MQE;JyrRH^x26%5?28JT*9tPac&_o#y#&`OB1^#
+z)txxo3RBbjDC`fat-1yW7)$&%9sMd3`K|;b94(qyv&|}(IFE5BugI-2KD9#;stFz{
+zL3qA%Txs(gj4x0YUB;123Y%v=;x`gcQ7rM>wZc;9rUZG47}tyXh~P(QtKcLlZj@=e
+zG4Ua~!PLV6>u}IlIkyMsEO)FE&7%7cTS-dSu5?`VJun(81umAlCKc5j(72hmAI0bm
+z9S5tYS(b?jf@+vaqzS!jAT5*BW2i(Q84XQyO6pp|(cX~I9@UadQ^orM7A%b(@sgk;
+z2myHDXcxzeRjmvfaO8u5dY+#{%Z<D%;a9R{BENL2ARnrjwWxyS3$_=O$FT#RZkXE>
+z5I#6cISy$W>l^6VMB&P=FK@Yhml2HtR6pHmT6iAI_;|%p)P2Ja2k4Kp<va-3f1nU9
+zC`))<WX(-*d>e(={V5>Hp03%P#*6G&T6=L|%^j>ftM4C_A&3C%Y26qx8S6*OGE%m^
+z>Sz0z+4BpoFb^-R__&mqfVP{#wN16W7|FfpS1TjM6D=BUT`{bNzrZy)r5uG(XxawM
+zq_LU^M-5msAU;GgLtx8dTL4Wugzx$1z{UA#E>!Uy-=QK6viK(e0ab;urwJ><qI@=3
+zs#Sz+vO@ziRHzgQ$qIs5FJU!oVJEnOcCl22^~VpeZ8_)6&5Vsy1t=*@RdQP7Y;&V-
+zQ@capvEQY!GMx<C3Q(G*psv415Cvs6NOGSWGwBo+uL{p!Us4fH33I;&EJ<%ml8C}`
+zEkTBCK`{l}pBuq(FV-}-k2_|=YgV_AzJ^7_V6a4x{_De+9)Vfpp^*SMzc(z+vf^MQ
+z7<HXIBNI{jZpWvTm=*~!=_I1z3<<9w@wPx<9RcrEOXQC$I<Tec>!a9AH4RSaHCjCl
+zGB_Fkfg&`9+vjkIk(W9!-#pg*SK81VFuc2*H_{?59S&MeUWmK6qJK;iJDzGuL9vjK
+z!abUU(IB2R@~&~$VLqz1Aqjq*@MqD!Y<d8l*z!#npl19wzG{>brf#dmv7Ld0zysH~
+zq2@o=`;^?bsRzjwb*~?9P$$GG5YW~L84oGd@%jba9mV^OsKwUk8^Rb{>s;o8{wGE`
+zHx^MC);`~s^^FGzSa5c=q2(9RUx<H)Lvh%ArFhM->%2TC7Uf#1$>5OuXe=%c%BHLT
+z3{4*I9{=ooe7+)!4!S@YAeo(2{~`gi5ct)V;VlgbPsUFXaPwp!-PidZ{9I{|-owf`
+zosDLchqjHw(K=8dkAe|~E$CeeKQD%?cg;kFg>yj)cg;fp@^69?3xf(7E|PD7z>ynm
+z_UH-w{sn<#|3+yVVh+Z`+Qp{I^|^N}j4*|l^P%RXhuZ&!WX`&}8N}}ob53k@eLvQp
+zka%KXBut9scLJN7bYQZw^<xJ$$GKRH+@_c@*^lhsK>oyk>Dxt<CE7Q_O7Y^gHdvJ4
+zjHkjcLFkdR-|gH)-?Bjl@f!lFMTYn3fGFjmZ^eoJ`XBER6=R!-iXX=`t+o6fwZ;my
+zIO{6~N0Wq^GS=e;j^Itq!mhp&gknu{w=A@~V$yPudY4vd*tnE4bZDGq^UOt~WOJ7S
+z`Q=`dXpuB~i{a8|S8p7p#Bv~m87XgueO>KUHkuldkCw}fiU7{kcct*~B>NY8C5Yto
+z>uoypB$ex|2|Qsu%Nm1bCyDl#Xu^mx*UXq5;Us5DChUqHc#}<0yM;99rf3_oUHUcn
+z=g<%yZsTsRTewX&Vfi0$%E8K@^>6?~^osY0nlL!#s{q${J!dv<jML3_VLa_0?kuG<
+zj6X}hX?Ql4GYfyMvhe<J*_=XCjos__Zoxtfa8SP41S%RI<c_q!$PAw0uZ~v2Gcu6T
+zji&B7la25s@ZDb1=ilw8V4b6L8u&s~cL}P*EDG8z1iw?q7)Lj&|C6Xi3rTAh*w`$R
+zDtoJ<Ho^%#Qeb-z-bJ1cMU&+r5Z83t3cOyO#J2HTfN-VDsuk$-sz$-jk~x7XV$R{;
+zWFY-HDl3G0`y?Cl2rDYB$LUTejoS_+6`#887H>`*_b<P!Fumr_`uxGEqGHOC2uDa6
+zk(*$B_TA@;pH)80weNx|_{-9isWdtZ`RJ)T;LFG@;z<J22X>6TWoU<A2dPrRigTmO
+zmX-mp)@r9xzIr$d5{`X85JOObvD7tl>Sp8~L~urY%)7pTc?TD65@1gKQ?PsL0kU5l
+zy3TW22|$o#lWwG%mxslt-$EM1H}M?M*ecAzLS+2QaFelyY!MzKO?c2;SlG;uV~kuZ
+z1>?}TtEJTtK&E?9LggN_B^ep3K?3#lyqemBNbQni7Fs6I?cmue#qq~@N1+100Uh;~
+z5TUT_WOM!&y~`9kL+t754hz*_3*T>7*&vxtQ)s&%a-!Euq9&_Q<HPkNw+k63k0+OI
+z#$f+kJ`_K`UP8|&DFj|)t;sC0m|x^iJ$?nBff*cRNO`Ym;K?8*^#Jn!Zd&X6zyyDi
+zKz&GsBCm&9@mUi%5-A{c=}u0Y)y(GuMoCdSRcjXFo5ELdkk#DepTd%x;)M39ZUbpl
+z0gbsRzsD<TBehdPbo<C*P93%p$7)Jk7o%K726>ZDVNpEWf0CLus+<_{T!X*-ZwZ(z
+z$ec)=ZD2qYSOuWpas`OWnfqLno{Z<%iJmpYeZhR>Cm_dc(V;e|uAEieV?VsZ4r1Tu
+zIvnvqBhb4=tqRCaktQ*bBy%%3`RQeLB4shMR1!!=1nQSX;Ng$a&<1f!6t2O(b+T8A
+zs}@q{PAhm_T!wF57{R&ttt}Ktj=eoCpTe}Aze=n#v7V`pRJ%OCo>c6B3#ubsOZ~&9
+z3^~4*UiBN&j&p!0d;h(j?E7GH(Zyt?)f)m=xHpb$8`~jp@AJe1<0qfT8QG-vlmIqN
+zp1}9tJ>&|+<e=6R@I>xGCK}5L1_yvXmME<6a2_QwAX%<Y$TUhxYm(%ro0_tbrWOgI
+z`<p9WnCcj^m}zP^ZgJ{@aGD9<Hxk^*wwIUH(sKBF+lk-{BYt7392!eH{`J2b)!=VZ
+z{I(q5UYdDMr+8bx|NeSe-Rs~OnC0<;8XGyvO%xB==-9*=kFDB|N5apUgvH31wm$H5
+zC)Re^R0c94y;XiNX4yk>?yfuI3g=cvt^;c!)rMRkh9%Q}(X)|p)4wEBL|%hUS3B&M
+zLj>r>@qwcnGeN9Aujn#L=z-`jZpBPU*G(>SU2-u0s-1T#%c=ui2MEv2iakcB?q52u
+zn}Pki%ZL)=0u?l1F$r)F3wGw1KA#<=;RVB>mYk>7%ad8+)TIwje=(CnOlOa?O^Ij{
+zij*=P3KCBsmughLxyrg!$~|+mHJf8+$2Dy0>&p{!kV4ywFJH79D3n<uM6kDv{>4ko
+zoR^T3h(M&0Az#Vn0jiVph+G>w+>nyvm@-NK1t$Dxqm9o&@i=49S2iQjCufJ{0b2i1
+zoGN;<H#%GmTW@`kRk<FGi4fv;q4Rk;{xwx<JvZVg#c`#(uz((z0PpS@;hh^da>lC<
+zEL+VnZ88w#$dsjX4d`RxS>-v|V}nScTC3GCEB$9s2JvLNCTU@Xt-?1mqp8K<v#z4p
+zok6q}Ib@@*CxO6fNg73ay!`PCMM&Nkeg!kt@C`gy!J}Y9ZHOO78|`WI!5JeQ3Et=a
+za=Lhp%930sxm|p75Ua(CT4BFno7@7ds@t19u;3Sn6}z|k>l~QBFXv$LXqs2exVEGs
+z!+O~5b!=~xM5vD)!>>&ST)QG;VRrGUvL%k(+D^q9C+wTTHz=h-Zc($tH6x-jt5Lc*
+z^lnJ(mSdl=-<xdqaW*Al89W~jMtXi{ZnffqL79%Y|1`of>$T<s4$?%q7w~VFIv5g3
+z;fde_<mlYxTQ4exrZajDnK&6&&RPoE)WAc=$s7R!dc_Z{9_r~Fj_70?`==2wJ)<LK
+z1+Zn{UbeT2F$@xvf6a;P_6bUAo$O)*KC-5-9)9OjG-J_d2(OfYe7b6Kp;PY2oTX=T
+zJYHqN(6Nfe(}1L!RP;2R2nLt!jQ9NIvPJELhFJj|{EeB+?e(vB=seoQS%B3Y?xH+l
+z(>j?T#7-1Q7*3a3+Rs{A<Wcf6r9jm`4WQ|!1HY?nhSj~XF(->UwMKi?9e%dsV*RbH
+z4ZADT);f$P)ascSQ`6QM0$cr#2%+WDHA&BVhlOz_hr0bU-y+QHl}rS6a7Kp3Euz(B
+zpj6C6gJav9CkF8bT8hPUj%;Z|4QvdTZsZnMTeXY`*rh7+JDw+28z4l2iZFt|5mDL|
+z5l87)XNsyjW&o!4$XFWPIu^zDwUB#w9iX8hVoOJ4#FR8$Ae@q4dx)gXC2U)-$xN@Y
+zUksl1*YT>AqS~UpwKTcNn5GHL<R=Rj@^CE2t!!$yZV|*7;MoGt!!7h>Oqb){qvrIt
+zae<g-?g~#gU#H7ljKBOxAoo%XzI|(^)5B8E8S&(U!~+z1$G0A#U*hOG)Az>}Gux~}
+z@O(bi_G&Yb<~R(S^X-HP$oDKRjr}X6bz;jK><F|Y!PY8-v`2F+<B@N);{yM0`sH<(
+z@Ez83pTO%<QO<c7!iqS#wa*gvz4I3wX4?i=zlj0}BZqs#%^%(#uFja+ZW_dr1ZyO6
+z$1&JUqX5;eA`7Cl`%)5P+`uqq2=0@%i@x+ujNz;JGn+^R9H@j`lP(a)N*p+s*4;07
+zXfX3fs^!z5!@S6oH8`X|vEs<Kz>eQcj2$_?1kguDhg+ZCJcv>r`$x;uvv8&o5La@v
+zKALWQi^WDyq#o{AX6?Frxqa-z?s+pVr&(`db?q;o1Djx@`!sYgMA^>H0*gC1QL0=h
+zO*f8{9oy#x=swLq`-B;eKR*+Ftc<rv8Ighf-3D9N3S41lF<X|_`OFKsp4*$*+%x{?
+zWnm3JUjP)%5jt!?U&ahC<=G-nWC`3<c|Q9&@BYgc>WS`Crr5SIkW^ZplqUsj%vl!8
+z3@A-zN&)ObVlBm1-RawY!tTE8z-x@rC(&O;c;O_vGai)o1np+Z$VC77jy$@`DEorg
+zGWD7SgI*ihl-rAApC?@Gn&cL1oY9qNE_i(DOvffl8Y8!;hoPZcE;v`A%;I29GZbb^
+zMx6Gu$-l*%gl}kkJEA$p24fkpv4K$=cY4~ouL5MRKql#pX@Y?O9!~e1mc0{7-gZCR
+zYnqHQVJ>Xp+!yBh!9n6}8L#d7eKOmva*W3r9TbFQ|K242be8jUhapi_nWU2ePtNQO
+z^!sX*NFUt2pi%l4J$hL?vq^-(x^3`S#@8JY7LN{Sp&Z)-aasF?4>0X^bQhd4FA+*h
+zFQ^0fp0{&fE0L*KjfmXeq2jZzm#>lJ_<G*zeBSrx%lD=Dx;^gQUZ01nCyXdT?9B#e
+zz3i+WRFU?B^t`v}S6#9xmfQF1IA{Xj(-kv1fF&O~Z07b;OgupBzd3>Ii0S1Pfv<tG
+z^qN6_%>{HK%gy`3+0}Yz*i9U7>F#C56`){eW~I%mL4j3THaypWN_{k2BWtP#HTR{Y
+zl_g<_9-}pB7h&{0r#c_@=B0Ys7E~JPC>MaTHA9EAc_7YGY-ZItB0g8xzkI0N*DkA{
+zB>d5K)X(39@X+V5N$y8f)HUxCoA=JqZo?bb_iaxWUQ0#isg=geEA%`R8fFoWxF66K
+z<7M0grVQ!c<Z8RNGoaarP3CDa^huI|no&8~(8!-Y8P6|R7`A&En=@ZOS^XgG)V3QK
+z<b5tIt&9#gTADV}E&2w#`I0Ub`J+0#3r(M6-AtzI`(=@MF(3y;zCB(G^oy3uMwm!=
+zoe@(LG_a2zXH0Nu4@oXdVPwo}=aP2dof`C7NAstAKv(HHK4CL<^jA4z#~1l-rE7g#
+z#*aJRf01>=1b~M$S^I>w5^Z85WiZ|dJ}2Dag>=MT+@w93)I$T<WYKX#xn7bnss?OB
+zzJ?0l)Sw4lAqVd;eKtxNSl-JB=3Z4akH<6lxLmk7fwYansyaVCU+?$Ej1FVs3qZ?z
+zlVR#j8*|TC(xzfPf32bP`1FOuD743-cUQYc6Q1$<QmUx}G?fhWl@`iec2n8&v)Zc0
+z6~a?(^ou9WJGVUdj$Q|jL+AY{-s~3Yrz!R>X(Ne=8=)h=4zG}AfE4}^Fb7m}K3{2~
+z^oCeG5AjMUu;(DnL`7mvGlw~c&5$$Ct4x*Ex0Wzk3RcK3Ug9JFFe7x^bK-_dW*qiS
+z?hkWKRHdipbX5q}o<*Mmynf;TXm*|RIUa4lg}v9KjP%5Gjs-|H=qwtJ8hSyv=`NEG
+zM;e4(P8a{WiSCSn?HZ~Sp%_)BZ}fHVe3KKfid}d6IzQa`MtEXm&nLck>KrJ5t^}Yj
+z1QHpv@iK>;$nr0YJ!TN&;*}0A@Y5&-kYq?I>wYSbe>P*6eNx*P$7XpTW-QyTeH0M}
+zdRAmib8_4(A7`R5HXDfVU7SKi7L&&Dc9PT823V0xw!It53j#s%XV`*(IzWJxR_w%?
+zrlo=an9<I9qV@=E$=j*OTE&g-I&5B>*JC6gNZH9^_%BmW9#w4cq?awAq+eg3_NWoL
+zrh6{u8_RkN;N(X<9u7f2Z!>*(_BGIaaD52SaI+x0u1-kiSbtjh@JF*9!>P4DyP4iv
+z4C3rnq%XYniLmH+ojPP!n3~*DOTP5Nb*Ya2-o}-9+}em*VfFQPpErzakv{Tc0hnIr
+zZEhqZhFjN*+k)HlF;t-@d&1Bd2u0iFy&^hcsn_l2M#MvT<Ywoc+4b*RQ8EbJKTp^;
+zcq3U8h9<IcOnQk@Mx@wI>G6tVn(~O7E8*{mqVNC3ZfX0GRRRvbrx7`UfWDglLsoHc
+zwy`mB_#bdf<$sq|roAs5u~*}-T|a*tHB11Z0wrDYlus@f({di`XekCiST@E)U;sfL
+z3irV>F%ty0b@{$u>AB{fz*4NQBTP!tAg7((pFdyVm?e%TS}V{--W{`SHdl`0-;NiG
+z4V!8$16pd(UN_cyD@MLnnjUTD`81c8$*f%N0XnqOi^gwPnU+=)_D{zL(^Ag^JKdW*
+zD`ca7{{jt-@OZgq=pN=RjT)}FY}Hh{(^<3{DO868bPLroXi85i0WEB<rZTY$qo%pE
+zcTAa?)=IQHnI@vwNKGp&0A}x{mG?z4%+FK(O_%R6FZDD)<=^hAi;5Jn)6Pp8PnG17
+z`wdHlmWx38yVg)$fJ`@~`2~D;)$Zo1iBjpElR~nMTIk0ELh&oV)<VY7B!UR{OwV}a
+zV-4Hi%ZS#;VsAiJ_w?p)b-8=dZDIUCGd8x6x99Eo@I`B?k#)bb(nsPRt<61Gsr~uI
+zt4%XgvqlVaR9$b$YV9M`=(?92w)eK`M>xLeDw-Ad5MW2UHlmrb(q5Itee>UFdge*1
+zd%HFYNqUdYXEtT33ILd8I8-bak9LJKe@)ND#Nyn5^_v?oAntN=dK&iaowy2!uU&nY
+z;3J1EUPrN94f$$ZpJPbO*eT9$xMxRS6#E!i2Fx3Sr1?-*vTd@ydI8KeH0Ie%yC+VE
+ztsAX02{7`x{zP@>wO3D%B+(Ka!&Ywrj!R}LR9YCX0L|8?!NY(Q_Vp?Y3!6rJw$9l|
+zml1xmW(!wmi6Jx7#Y=2Wn|}~>FT})$Yh_bjW4HYrR#CtkC{Net;Hs@GeXrJRpYVrV
+znWC8ifNtHbe-qDMbwQ?bxjgPfph=rLp4(N?>kFfDC2~0vH8j&yojr6D1>HS(hA2?j
+z6%DNTmm#4yxA&}Y3s@Px7XKPE<S07nH8r_#`Lb)r&A+5q8_!ljEK11zau;dyH%4Oi
+zL;K|Lx&%>5ylK#zAi23f4Z$$8f26PoG{+fNs{;i|mNf%IDc9kJp&PWR^3&yrr@EHF
+ze|M~anFLWF`>_y&=?7Cul(Oj(%ju#D7%rH`sJ`ivfVf6$rLC=&b#L94+pMd2)7E7y
+zl0hNk*RG9bq@v9Vi%Bmdr}LoaX{QGY4`VLyh>S(IP_Wp+>S-HjB7&-Uxmg>XL|z|Q
+zC!deB)EsmZ$h2+AHy@QPW7k&?v0C6kO}vo+<qpb5-?`_tdJC5wD$1Zg*aJn=Hp~z(
+zwqY!%LxOo^<XD6mz;?JDpabw+q=y+M*VA7;6{#A!yO;d<z}eGN2Q3+t{z&!9M>GRX
+zT=ZC2tB$K@zWQXq+%12GBVK{)6Q>-sTba4uyCC+jB=g{+OUhW3K!)VRNk&T{W5dhS
+zXM8}wZY~CDrf3Ku<OLFxaHFBrd|@EDW=&<MWc3y(_=gTkrur=f(Afp;^eMa?`X}du
+zo;MAFV<e3hbgRU1)X~VcD$`9<^DZJ+o$5%KCJNSB9&oz_P=i1b`sWpQQ<uG6e5o(k
+zNCr;J_>t9%3`|8N#hPW%Dw%smjHYP;^BO5twj6h$Cg?-n8Jm|5Qe1<fmpvQRsTwM~
+zbC+Sj2f~=nax^k=it{T`<4*)2>Ld*4rWra_P(Ofn$RSIreq%%Yp>T6>_TPkq{A2rc
+z<l>3Gjvvf^O;u<l$#y^w-PqXlunmg_E{|`+7fLv$LOhi-U-0RTjE``-h-*Phz#QBG
+zp__g~72DIapNXxYUGl1)UAw9(ZbR#>PEaJs&qO_qgcd59AE0GUL5}-IYYQZ&(v}7x
+zK@lurgjM~iG7fw?goF5cdiO)MwTqAeCS4Soc(+MqkvYeo`&Y$(Uj$wBUchKkjMxb_
+zcA8m2*lfqUbd0fr$kL>mp!p6`5J!o%px~l}NI<_+Z^oom-52iy&l-$?GiW$pm~i&4
+zsts=B+0=p}@4asMKnn*3Bvgoa1H%U6q={n^qe4fg?lOW$S(RBK_o7hyosA6f>gcfc
+z2^XdKxR#-W4E&-=HK2c}E%ByO*y0mo;NA*VtNvBQZcRo(WJ7-WwUaPIQQXXjgn83n
+zFEdss4@F8O>gd>s7#?zy?H2H5sPuo*hGRHD)~I?JGozDF>IPIipa-D84Y3TT2rIAA
+zQ%;0GnCR-E-nJc_Oa^3WD3#QNll&oMz$~A3RVg$^X1$8i7Mmm_lsbdc$pwf2is0w6
+zc8tLzo#W>q*~tn=fq$|qq?MdYevuYWrOD(VV!>Ea!>DM-FfY=HDxnG9cQJY|MCH+&
+zgNG*+M#VR?fFkvUv9e#B_f!rerPhVgfW*GAa@2T{Xy?YZe5?3iDJvrGPgLF)OkMEq
+zkVt2n(i?A-Hua&rod>Nra9=2ppQtpO4n#2aE#UhPrb^#%^c8IRXD(_4(jv+VsQ4vg
+zWiPO_^g@w~q|9?UYe=;!i?@nC?m<avB!9E8+EXrEy2>;Wl^mF}hT3EPvF?krwxNBd
+zu<#w%CSDAcfv7yz&skFt^-x@?t1bbJZYCB?S_%Jrg+K44;8+|Q$}%wwW}Qpc4|Uz!
+zP7G4c)5lmNRYZlt-2Dq=qmtmH+eyO)E)l@G9^PnSJ~9*V+r5o<<3PY?^rDovhG`*0
+zAG@U=B8K?a1)9H_%e3s%Aea`2fJ|WduYavZ;+-O3qv9*Tt)y&L=iDfJuq27BbscVA
+zQiV=w>dy4mK&2#OT{J@uDx!i5y9>`-TgR><!y#t{8~>DwOv6vJI6%e466mEd9xBVN
+zCUF@@J2V^N$8}*A$V-vRq>epEuuRh)*ZuZj!$9E-vKNvt7`*!=A#w;vbgrw#Woe1#
+z*+<|prb!^k@Jad(@9k6Ugkg+j4Y!5bD_qHf{A``KP#J5vq+9*Efpg)6QKqown@)pZ
+z7PhCe%bzcsJW`~EEqMAwJY85IDVlO6KT@*;K@etFpI>u=WO_&qepv<d)>sxIS4%xH
+zQ}fY63oYy%vgUJIWFyvgxko8?J;>$c|Ld@JtlNuVD>XegAT+@z;2R+4pFH(LHnK2>
+zKHeERQ;u5X-1*%;v65KZ+j&Yzhz4FnKaBEWX^Y@=IYjSInI4iVBJ*AIjP@LPR}<hk
+z|AQox?eB^z?$W;t&j}MDV@zC%QQ#wqVHFh_NLfQPD_|wgfaKntP+fvDP!s8#Ot!K%
+z4?ciiXRW|Ef@xzmL{_sLwgF%_YN`H9B)o!Y0!gXSnDMY609W#oNRL={;=g~W$T3D2
+zP#(PMk4G1lY#0~GPIjN%kp>ZgK)V9UGJL?(7Vk_!wH*CD^t|bxG8^eUKT(gF;w(tq
+zBm*G(vgAwy$w5?q`T2mL3wlOlJFD;;cs12r=w?C~Gvm2P`!B9m4u_07zmnEr&pW2D
+zvmn2?RdYmtI_kd`$rxhA6PeOpY!}cLB`UR6f>K4`ufRjkgp&`-qtf|e$C+@lU7ZU)
+zL=6yPUQv3H`UDkg&C5fWq_Q`C9mGqeGCO{`h=R-&rY?s;0wM2LOROzzj)*%umK;)z
+ztPC$ytXnEaG5H#{$@hcP(Qf-`ImAa7{mFrHxayDHGxlWvD38CXa#VgRcjm&U5>t6E
+z#Ozd9*5cjcZ2C&DUmEn_pL7nGb$Zs(m=DUhyQT$u{2u57ZK8lxqGy3;NPyX~OCVVG
+z{>3k%lWg|`4GtMjx^1^Cd4rNLJIL<5N_o+vYxLU<#@uoFRm<#!yMEB3J_^f;LA1`;
+zwvo-8jrnbt2g!=@x4SVH!uHGK@#;d&cBZxmcZeD<t1s4Du&&=&?({5CrYASEf0o#q
+zsWXFZ85vgLbWNz>`s^0!XDbmli584UI%!)I9h^SQC345_VxRZ#Ao4!Mp4_I1PaJ4=
+zF|q?<tN$1;IK*<uC*>+w5*g?wJ{|WH1Dr8CBoOc12?V4#A<$xdwWOGs@x@7}==3sy
+z_}ETPFr_m1NPGeXF$_J(k<J=(I)HBBH@Cc2Arr^nIj2aV+m4`Zb>*mAYU~IL<)M&f
+z6Wv1fLGL&`Q0|o^ES2aTAHZ2G=21e_S@5IOILW2eS;_pN3$7I^q?`QTy2*t{7S*3A
+zdBmwb5{q>dpBJE%@j{`bi$SUi`6X(~Sf`79MTKI6<R>>OwWe5rMa;w0RDXMT;+r-R
+z`Pb+>R!v=kPMzxROh~^%H2?5)Pr7FOco*G#35Vg5P8539ts$M`!U{(o;>5rDjohyL
+z+rxF2CgyW>_2gVan4^Ef5qGJ!RFM65NqrwJWU@GlDtrKx$c2f>!1ob6f{RP3(vM$0
+zqkY)<Ms=HL{vYs0DB13FV3G<WE16369HN=mm?}{e3VAR-Ad%rFQO`De{u_cuYA;h-
+zw*@+WXyN?3ag?cLF2WOV^ZzJ})R?qO;UbG^Elf-3)|rsx?yA?9n@ER=nknfeP<V7y
+zBNx|yVdC{w9S_-5+?W|6x!2oRGmvUsNgAZeJD`{YTfN<G2YO7gUQZhIV%QSbPiwOr
+zuTL3}Um;YwM9mXgkT|weIA7iHu<6ef6)#~U>o{>i<h$Q|>m=@CM8j}h8-kg|pQ{&$
+z8fStChsH|0Tvy_1tB%M-^ND}t&uH26djq!`uag!rE=X{~p$+8I+1SKE=GRmO{T?JA
+z{OBnR<(<*BH}qe1Cj@aVF4n3f(L91YuboJlZv&+T9d2|NZb1eR<uk@88({%VXz>&x
+z#*2u&FEnPeW-NqHIxp`p-HNA61YsE+9Qf~0493I(glL?Or+y^X5yg;QEUw@mPtGpR
+zkE<34ZTlvBylUtdm%Sn;Crb(Chw)Pl-Uchiz=yIIr~V}nC_+fhL>V?%CaJlDmdzh^
+zf0NZwt{~x;%=av81R)Vbx6rieC)4rZ`R{pTWh9RchF~9o#G@@a%0r)`@-3+*f$iSV
+zP^E+c#^&fx$#VfRA@gPer;vH$XR(UVYMK6z%N2|+%+PS8O{^bh{4!BQE2D<>mAU;J
+zi1e~FxmXyfaNVHjE-zSDl)$38q9oxrgV5wrZr5Rv=@N!db=8O$|A(z}Y7Z=2&v0zp
+zwr$(V#Ky$7ZQHhO+qP}n?)0GDJ?M}4uJx`*w=N=293gBF3^IeB{baj}NH_@6Pt5$_
+z$NZah?ldq`rEK#B84*dXYa#SMXeKeDA_15YFhn`>gb7cak<AC{(vLwkcqj8tgF4Xx
+z>}?XrCAwu|YM$f>p$C_|04b7Q_ER|a?;-O=5LO5mbKP5J#-SrRd95{q|MLkhi8N9N
+z)B$Mn2jT-$_DmVS!650wI=)Ia>5TM~BX4cWrvZDxxvr-Fb~Q4^m&c;&=01f)tHT~2
+z9vLs0JJ6p9fzD{-oHF1gUgdZ?MmVQu`@D;o?w0d`6%71am_^pGV&@S6Uz|I=K-Ye4
+zT@P#+`=o4mmq90$JN6P0h#c9Ge1U>pu-g$RwmJ&%+X0!FR0Qi3mGhFeT1Zk9G+tIW
+zbX#UmgAj3F&xhkwem?J)V|+G#-uLuhw+7A(sf1MCjz1g+q)g91<S=n^JQXoiAuNP5
+z@^@nxSE)GkfcI(rog+2Dc14Z{l6LV;0pvR`!WZ!$?X2LDPUN0Ttqk_k$g!uo&8J5E
+z7+t{B2d0XfbQ5XqEJgWNh}p;y4>(CMlIzu034nsN`2jg5{NqkOaA-3gqiY{(@c;~B
+zNPfiRU|!Ra9Fsm53qeIty3kpRN047ILk2#)9L<5w`2LfTJ+1X*SqWDVzV|h!u<>nA
+zaso8-O8ld~c$OtIWWKfggR}8>Nr8;w7wsoRCr%F;CjC&B$pVQ*1)!leCs&m_gqnlx
+z7#s_;8d+RmqYw)$+{F3EAZfEUh_LB7J~`b4sBwnL5+;7=JiYNvoT+OkPg}FdZcWS*
+zq3XS>0&GPd1p|ZLu<Y<Gu=q6u`>mo#hCs2$`%n3e<ef#gXQY`Xb{86}wVYBu5$(g~
+zeOyLsD4ae%OW(4IK<*P&b9$}?Qh*u1X*AJe2Rh)-?ox<<iM`$l{u1B3OCxaaeSxdG
+zWqo@JmMArAx}(hv(TMTmgc3CiA7+tANwW`uC+W@eWF8EnaH>E3$}+>SnI&1_0`kOR
+z@S&8MGTt>Rs~0zB4<J#VU%$nxVmgfh?w$m{IeV#+BuQdO1lKQvujS1)TEui|G*b(O
+z%-BGbT^7k0zsgyoHa8y2@CfKTM?7Ovnwzy;TA1^wwBfd}0=`6kZvM4q!r&SoDb9%4
+z1V6HX6VJXqY2v;Oew`CJ$IKWrg)Hy!@P2+kshmTx0qIKHCJbZ_LpY&~j%><mQ<1TG
+zreGe&VZK6td3P54`VCTG^$&4;S|_?XNw1|AQY9&<R^q2T-I75@7^t<aTyQrcK}QS6
+zC-q#Yz}*}-a9S-+apk-kIllVvKQ#$<LTX*|Z{uG16y(8nCiv-GpRMfD)%8nW;r<9>
+zadF&3Fi)Ah9N^<**?w#)6)i!=zh=(DJ}qc$&0bb*1B1&8lDnY(XP^BT3CUmpiOugh
+z$dmW_y9anNjgwxa&ypIc`;F&SSuZywp16pMd&55%EcZUzgne(@#x~YsxULV^FEq=J
+z<s};1XONjw^*=D`hai5I6P!4h7cgFXw`ol6rWJ$AoHV7{RrQ|UqHZw-y`|OPrD8oJ
+zB4ixsa{oNI$~O{H*lStI0?D`puF)YeVK2;&j1hD!R=JAt`vXn2a>_&;^X{eP+heGd
+zK{y)ya2+TUg|ACo)z?=LY+=dRhu3>eSz^oXUm;tCUhB%a@zROs&u4+&pK5_p0xqpZ
+z(Zi%Lqii?yCx}Z*YQDI|>~Y(dbXKS@Snkt#ib~wIqyQk*X0N390rOROPM8?dLZ^S9
+z7O@Hn7EZVLxaum{Mcp4=(ID+a<KKOik5%D=6HQ16{$5pygI@@KNnEqo;H0W>>A8*%
+z%b%DN0oGRve{=1Fjn%w@nnBk3y^f$s8~AUhgAJon`N$?^^$w(O=d}c&Da}bjXSjEu
+zGm?W$Q%pwzm4eR$8({f$vI8p$qhCV#fZg4*bTefA+b-J!GNO3ZH^NvMPI;F*<3TtO
+zRut-pI!Q+%7xG-q>IcB$ep|XdC<a>sDFRIqJZS|3sfl*$n)R@%5Fw3RQ~BpG60$s7
+z9o5}$uP)lS#kIh{*CF*`9t5`y*)53%xt*Y4-dP0LU6z1Hb6GmB8SzWjMKBA0$%P?r
+zkXtZKtCOn6q#JYEMLbFYnY~ZLGVN_B1W0OA43y5GeA-dk8^dzbi}Lxa;--mY7o9Y`
+zh;pbnsE9W%9<Y`r`UhoZ5-{_H72<8;n7|U7duL{(=b@(he!#pd6G%HRAy`MyGmqV-
+zwhd^oVL&{_!4rY|Vf^$1KlM{=G19Wgik{5F8~eLq85o$~grmqfiYRJt3sre^*io}w
+zkVQY>Z@v!6d~0!{!gnvYOPlDH3--2(wu^jJZCpX7?eD59s+)l#5LZe#c<0jhSrEE&
+zUX(V3I?6c~l|HE-R0-8MmIAEjKaQxL?NNdmTsk0A3_Y4bLHtHoZQ0q)OL36gpMEao
+zw49Rk$~Ys3iu+dns^UdA{!4#Sd@cE;2*nR3>G^;r9bV0rs0{HSo-%518ZK<Ej(iM}
+z3iB8dN2{?0QsF1Rq8FQd9)%&leP}G`cIo7uL`_2}f%5tlha-N4H2P#kiUA!<a8W5}
+zHxOpCX6OS@%-OGm@tgqEZB#S2YTl8M>_j)7c-L=tY?K%q=P3szSzvJ5y0Rj9yyi2G
+z(fQ7k2?&Mr06aE{s{8N*jnxios^FXaZL+YUabBDl9fTlW%5*pB&S3258+0f0nMrF+
+zbv1qZwTF+$dFH$a*`E2>B4|K1eUSe`6)LG$BKyff-v8JpMSLZN9C&}%R9!-S@<$*P
+z(Lj<G{MNRC2lY<{*s+;3PArN;o~*Wy0vt3Jr9+R=qRFDm3xP!!qeE7u1FVt&8D5Kf
+zQ2B%TIGu$e;znYAi}i24fL%MzTY4b2Y0j-Uh&C2+({szfyg*U5bf_;ZDwz6Mm7qSr
+zc3PLHI*oSx%Q;zCu&y5MDSNo`?S8yd!ptx55<5$||1?Zub#J>&{$S(}i?<`n9?#~W
+z6y#SubiW;hh!6vON@;wTra5R<Eje+2;g5$_Fr4Lx`#b5gQtyWu%{_L)Y@$o6j<o+~
+z)F9dFSPC}vcbi!Zf^=<UY&7JziBlO9X54=2(~khd;HwFLOEO$JJbjfw&O}E1XGTuQ
+z3RKQHnr!Q`%b0%u<X`u}P^sO1!HU2d9DswgsqeJa^H4x@FY27|f+QYbJeWms14~L)
+zjP;GGHM><MSmQ}f4d*QV<V_q@%VH?GSZ2AYwhdcZ8bGrUAUfTav1#I?<d~ru%5crB
+zTHb2iS+IYXhr8WU;Hb?IAxYgxRh)LQU<c(22TrsAeK_6JGEEqeo;^kmVvbHM(l}fy
+zLWAg)(`q*c*m_5DNm*1q96%5WXi}XtP6k=i&av~KYpiKd1H#JW&_jTfBVMzLO_2{m
+z21FVB;l}y*oOl1)`+0^hwyi|;ka32I{Ku*drNqd*_t*eC@|H>d3bS-8Rfq}Gf7ba8
+z#WMKimiWCwe_^+<Xr7lxKKP#!;&%B1|F6LALy`G^`DKjj>NhzUa(2W^3hyZDlnK>&
+z1lu{*a_Ki^_-LO;L<21mhA>h8<L?CG)Df>mq@$;EWe~5waa)sN%7RFe_wkA**D(+6
+zxTxWTqSV7484L%J>Deg;%*h~5NyGN$n{#2@S)RB}Drv_=$B!*wh{yNCF0Cn6?vO@q
+zG>|fU?6|4zMD0RBh|$*Q12z%GZlhr;He(GUC3(Q??6R6gUIe{Dt->QXA6JmAn^xq)
+zzP%|5DK@PlAaGVM!Al>Gx;USPeBye5Wd{ov0$Z$qI;uu+lh~aW$&9;f5~#&AH{XI#
+zG0C;eg+y9Bv$djkjS(pLg5y}S+06?7S~_bfBIIj*v<NkTSkKK>tE%w++mWd220m@I
+zFfmLJ3@!km0VqDDJI3O*<%sIsi&C8yO?!Ww<JppnH+}+faJ<&^BAL1wUQ6#Hzx=CP
+z`u<yUi;ho3^BP+zbBdN~-kpUJwoNV@9jm8hWHh)g$Cv)zwR06|idhOYN`Bb$!?fMZ
+z!5^O~10miQrg>HP45DhQ1BvHCHIX=j!OY97+hPa?pKpnesvfxhY>QZ7z`goG?L9<`
+z-C)7BA}3xCJsx~6)-s*(gt1Q=A7DSkoUD(7WN#o#u#aHq?~fl`k@7rlh1r`18enNG
+zUN?XW@kQI|qF7DlNSA$}fQtkkA&Y#xjb|$83?k0N`sZwH7{lN|Ktnl0L0zz07eI#C
+z?LZz4r<q4YC^rMidUGgD<fv#{NkOIuqV$9a0`QR=rN_9=bV4%38&-vj&Mi&Et@@HJ
+zI#Bb?K(UTf;0muV6EOGK%l4q&dCIig9ng!f&vw_LhJ$>eP}iuW8E(7k&T;9R33u^z
+zaoJ;a3+@k-N^<|>l(iIG?xTOb-o^<mc#K%NUHla0c=lQK!26R!1mnH*@k$E-Gu6hG
+za-ukDs$c_(73+EA0yF+igIi7=$*KB{1N&$*{GWGemsf;+0T2z{5xC*{*v8MKnTjUk
+zv@xO@VXx;5D#su{Rw69_ErxLD15P6Q<SV{Z8s!5W-sK85lBG76^tbrS8WO3nYVCby
+zw9Pihg;74rn5nmAO&SW1dHL1j9zq02?&wD9f{KO1o>9hL{F<KhulOUgMhd7e+Gs|c
+z>)SR=v8xb5X1H-I185?0(kd-^s8p?Kgi)8+<?bF9OIYeQu3D;nb5U0BDG?P@)p6m2
+z&kwpQFcBO8u=mVfpZVK+^Onm1<d1@@zWTNd*9-^17)W8?zRcV{d&nSdn{<yf%W>8u
+zJ&+zuF~evii)^MuOe%WGUS$^q4nr0UEJl*)1gu79?iJ;CJny+{pi_dYiSWAaW6A8@
+zO1EHreIx*;?aXHnu4l`m0c_*v9PH2tGgAptaidBy(Vm29NXFy}4x5Yu*X=-*mR9E-
+ziW`Bpg-L`8wgqD7Q1iHaK3c%mEo-*VCb=9`x0JvGaKKA$jf~DjhgMyD-dSC(cfOlG
+z;uOZ(KK`{FzDCOA-u&KrM<i@OL6pb>*r_sqmUrpA;Sk60AB>ik*c38%Xrkii^}ca+
+z+Em7!@~ulY5d)dOo3+H_Zhg#=Q~^GwM85~;k^@k3Unzwl8(I0~91_-roeKU%zgp%x
+zKIW8j^_6p{F75`A=yEj7*<U2{%bLs4OOqM344EaG$4GG%Szx2aDACI)s^;TM_IpKO
+zJaEI&VB@+}wCiaWyD?wJY7TL8<p&+4Aexy|=?rS9VhBS72e&$v+W1%(ZGh$C=DqNL
+zJ^*2l=(B2Y*lRJFucfp3xs#kOa-}snyaBnZ!!5buB1I?FJ4(#5$P5$pIkXHjx=%t_
+zGpGiFqk?+gJCqi73p+k<Z2a8vN)*-TsvDc=g9b(WWz^{L2u@f)N1A<PC~F#GahWp^
+z#adQ(9yqinTen5KslD6jz2lP@LutscJ+h@XAIL<5c!s)DE=2seU~TzSp5qIG)g_+x
+zUD;t!d<&uo84LTjW|3xc!74o%)09hpAz@Cy#CpL32f+Xv!|JRqs|YwNTDEa8HFE9s
+z_8C|wUMq*VPi^U}STBAHh{FSd$}b@t{JfJr$P$Eq<_Mp-BeOU{ojCSLkk-PR5xKkr
+zI;>ky7q33OHp9kIQq~GKv7*9AQk2YDH=&3UsbK>Ht{F{9B+w7q|3K$<pXE*Ar7n`U
+zDg6~T_Ug+uXnrepW|z#>n<C-!y>=ggHLXe%i9$A_V)9JlsY%45L;Z&{)AMEaSYhpH
+z^1;<)z@PA*v0y)`Gc{^1aIoDFE>T*raaP)1FLvV2-Mq2N?3%LWIJb=!Isw2!DUiz_
+z?tU(<UZH!7l^PG>;GmV?#4nV`xYY)bGisf!7qjPJH*yZn`)f#h1rb@{pUmoo*M?oh
+z>X{L$Ue#f}6^c6%wv%d`ei!nm*Y#7kg0l;Oh>9c%7Zq5Vc@%P@xpe&w8$rD{o<44A
+z3BMD}%X5(jxkIKHj^|wl)0p-8P1=cejxmsi?cHgrB}wKA3;9ygxbdw(Rv7g@mq~lb
+z#c|sDbn6Hf$A3QDg8{BRD|ngb6s=`U`Dc`_cJKi!=t^bd^!l-;auiSDY%?C7d*0@~
+zP2Pm`YJO7%;*EER2DpWzB^p#zGq|COf;3l(Hczk2*1IbAQtjd>%#Pyk@`hMd)!G!X
+zlATHt%LyW^caX<w+OPxu{ZuBUBALuA{5+F0r7WwYd?O|i=VO4}1uc)Z*CC*`cA*S*
+z?x7q&e_r1<r?k>cc<peBQeXi3vs?$sv0P?fYfFkAxve+iMygDiJZQ#tS-s;p2{2Nj
+za3|D_UqTsTpTf;5Bc%9YjZ-EIv|Ir6)yZMJ@o?}o6!K^Mu;7L&LrTT^v7l_?zC~Bj
+zgold3vOYe*0oKx5hhxz4+SJ8rWjnh13vk1*_FVOYGhCDpGV3pX8SZbHr@Wh-b(#TU
+zp`gzuq^54&eN|FJjSW;ap!)IS)lE5UyIHybk*YR~fGGKAQL`>V?dHfQbbODhBM>fb
+ze1$(DgY3-OiR_>bUru4Oj_%^zl}bV*GijJ1Cx&y1XkvBb@cklv?^oj6&P7F+L2T!e
+zqD8^C`p}pF5+~~g+8$=-5!JnH!SG*e8w<n)g~FbF!G_XB_>B>QyAj78Iq{X($pPUS
+z&#$u29qLluTV?fZH8aWY9Z!w<SM5&9Y1y0TrhwLB3`X1Q%bfmYtDZL)7BZW^iUiq}
+z32Byw&#aRazK#GG-u!!h4W6G@QPR;aL;EDiXy?)Y_RXW`tv)?cB{oP#QS+H#IZ&3a
+z*n&tr{b*p_tTpl%(ngj4xaBPm*Q3VjFLQtym$dZ<9_kkUz(Qq-t|a6wUT_J1ap3gq
+ziBS*rSmmaV&9W~c16$((Aa(Y|VXrOxgd1Oh8trxtOM%}`&O-RUm2*rEq_)8sBJTF8
+z;$GY#{AQHOx*QnmgJk(Z$g3xQ$RsVBzc|CK4~N0u^9h`&<x*#)cBwjew9$xynJ{AB
+z_s8Is>j+r{QWA8v^Kk*tC~zptIs-+g8cxEDN1gMp*wo1Z^!jb53G4VL>h@=Kz|k!X
+zEU@CN-kHMVz=p*%j*<-FJAwoln%LEMOaQJ!r&5wbmhtul?HdVC59_u+xoS5L56$vw
+zTF`0vfjF~XdNWVHUl6I81f%75CZuOYEl#q0T0TXbWK2G(ck|<5dYcW_l3=z@wIY>c
+zE`<=rr-#(Yt*_epoaGP<?IzK?VU(;2g4Bf3p}v%Ff}>dHY8qqfGZuVa0dT+pjGo$J
+zzzPNN@q*uiTMzR=KmV>Xx6@9)CN0;@!ZkG|UHhk=nWG;E`t%1@tdHVE^he6MbBWKg
+zEsVgG%Uis`bb@{oHv`WLO@yEIIg{jEjK#j{KWPb%^n@ptsfK<G+#3r*v2RRK_ZSK0
+z4xn6LdT<I)QyOeY{MtEo*T7?W`!`i)l~8wbu$JFX3+tTmFxZ$E!Ta$FK6X>f%%URr
+z6LXgg;N`Zo5$miO_H}1UxZSQ8HEDO<+_!o+ynOvCxK|F=0fVzPvh^!_NJRp8jA(+q
+z;ZbJ+hM7fvKii(w+}HvMk%YxJLpaQ~B^y*RT+SDSfbZnmxJQbuk(Nz35Au;-j#a4+
+z1F^@Mv#uf9>S$~e17MBtWmeDE++sJDUvzI}*i*)-H%&{56U%CBT63bSZ|N&T;Z0(5
+z0i)1%@`qek=Y48Q#aCrZdKzp8Gc@JK4#7a~p=u|lS~5GrE!wt8Y}L?Iu`W6-W>=Fs
+z+7}|=TQ3JkFTSuBp@ilk5qUztms8@NmefT^$Ih1ONjAj+#UMJfBenP^q}Ivb2=Ys1
+zAYwH&CEJY7-0nptHq7b_rhsFyn$q8C4OS;=)KnHvL{qA9o9A-BSkryVZijk&OR46)
+zoF@PD!?yAr+B7@t`k~yZRMwOE6F2hD(+${kbqD$2u|S?gmMxt0dWEk9Dgtzutv#Nr
+z5)Xp_@H`z7r>1tY6fbO=P)(i}^o%tN@!K^?+3|frI}k1_V7C?^ldvD;s*^svlj~~j
+zaTj3sLt^`%YT|SQs9?#y;G6rxyF#PwZfx;Rp*Fv1(cv~gLq3SiKPAu)MnQiB4sb{|
+zKzMdEc2jsWpldQMY3fsyYT_N74v^@$y0=#^(zIWSvRJy>mz;5BRI<q01F7{tnEtN5
+z*u!)cPCp}+bb%edZ{aJ<qg5}?BDmf2Isv_Al7Oy{-7`@@YTPQpTDKpbg>8Hw?<R~S
+zQy;`&wujrIkW6cU?g|bP6}b-9q~sj%nL7;QFK9m_Ln@3lF=~JZRW?jL7-<Yu`+a&6
+z%y=oe>!JP5iH*TZGJ>^VoY~LGFBTW@NnEg;-2qz#kW!C}K;tjO;2PWzcfj!2b?A|H
+zn<VHu88`*5k}ik$3h8kk8T_3`-tsh)CCFNHo^PuyNEO<e*7gj?C|yL5<Ux4Fdx7_r
+z$~iAh7smCcqDjUDg{NFU7!1G+5GPe(;fY(KUx|;bgXCtM_9eYkyjhwf`;&DpVPWe;
+zTW1TP8H{-cNKp;l*6JeS0Izhmm-yyUyI8>wOuyz74eRYx$!bTg_Ph~oLT}*qo@28=
+z=;dLXQ9o%u(O8PSw7BZ<jpNF`r(G&Fp&!Y%N$}*5S-NV18yH|4g`9a~17x4suCQfJ
+zelU!UOv57)=p-N(3L*gS?=1s1WWq%*H(bu2bL`Dh6)Y=N<7?8|1yAfwC<C<7@_=cX
+zm`UO{)RATl7R8k4;oDHXV%cr}jLz_s6GS%3m{mK@cm**vn#Qii(^IhlP2W|#u~7MU
+ztC99R7OQE6M*%@Op)W}ePu1UGH>6XQ3H#uuY(0vQFgD;gzM|P+KeQKyy=_dLxymt*
+z(y3P8b?qvy0FUi*PK`yuJMcZ}CdJ%{Dlr2?RI(TxkX-}EG+Tjaw-i*Z!I%v>wO0IP
+zm&dLr9|)N(L~VxjYO%d)Yx|Fn;_^M~aqr970t*%IutRIR$<2d8nme0g>^jE*VUHds
+zzG7}<#q(jdN$p2u1(PrG4WN>U_sWCn2J(sCI@-Ut#@|f~kJ|ceyOmNC_bGx)xCF~r
+zfvyc|Jx+q{Uh_&xw|#9pOY$%#sB`<(|BU4F;1l0AWsoxCb@FiC(#fe|o+TD42uDR>
+zIWv>4?i_juarNj-2lV2#B>H)|lq$dWD_6P)PLlhoFCnCsZfk{?T<PXI3|F_!>wC4+
+zI3|vytAHHVZuahBTyM1yK7x}R%Zno;E(ivW9j|Dcn9x&q8>90lDCngvRo5IzJV$RZ
+zi9Yc!`If9oyO^vUBipo%;a9UJ+dR?T2Pi^sgB;#USDn2xA+5(I!E(;#&xnq1Y$N9E
+zQXYkP#l@JSBY0~S0%kn<kzDvZ4aAWT<9D1W?@SN%#5TC@^X23fKPma|BDh};Lk8&H
+z(*z7<>zK{IB<R*yIjh5cQa&tBfK5#A+n{anRe>2AcgzkrU|wG5v#?|Qb9<{-bW|kt
+zvrBFRyI)Y>GxNJX$H)D7=UMW={Fpe`{(Jc4qfT$qN{|OFxy_5Yq3Fjep(%1F)3K`6
+zrh;-LE;2J^-?qf)*lLxr-Cx5Zw+R2mZkd+3Nwu5A&Y;v1!jpEM>7j0^tutHv9Ix~1
+z=9u8Y@m!^&K7UAJw=@0fXzHSaEr#R#il4bkXf+zc(@+o~QcG~M-R&~=Fcl`%Z_kI@
+z?G<Ndd$FKrpM^NhUVY-G#Xi*3P>Wlcndwx{lB%E7qE^?UJsZDj9Fcme)ra##9;`*8
+zKeO8*3oY7$?@X0QKLd~|il69$T5OlN4~a|f7N2ufyJh_})A#QOkWT!tljVx@{|>%-
+zhb>Ir#`K+GKSNE&3H>>=IA}@^oRd9BimhIZEg)n1D}Ou&lR_1buzdTeqK$}KEj}F&
+zWL04i)$S7%Q%&JWAxzKxsx1Zo9WkRNqOWzW>zlfh)SGu=dtond7QBh*w2n)(K50p^
+zJ%n7G?iw{m?KPTsyjxtfg=PcJNunN}GBdA%0NB>!Z*+M3#qd7H5pRZp&ha_x>ig>z
+zyEciIBhO6jinQ)k)+aIvM|zm5?I>zWdk@FwRf1gdwy}iF&4+RK+!lC*88_kQ8kW;E
+zlM`S%O4gcTs-8v2YM*A2No7Gvg3yzcf5FpKgTlI~+WZ3r>|Gs2A^h=lrC6(bB0b1r
+zW5S%vb#B*ZMT#c`sI%c+F5GP=Q8ee6$oVUanJrQ#!j`%eImHUI+S=@YI&PXLiJ42$
+z2uyF{3Xnr^oaZPX?uCHL^G`Qp7P)*$g;SKx>rIG%8mCa?8Y^4-HghuYK9)uZ{6<*K
+z!D$7(QwMfGeFm8RS0ezD_i>Yq0yg60X#)1)Elu1dFc+_r6Ge^9)V{;PmceTl3C+`}
+zb$}Wxm}aVp`!p{f<MicV?Y&7yi=bw{=hMp<eJ!2t_xn<_{K<iE`a)xDXeggBvc%J2
+z)ld9u?AspAr^|Cbup+Ni?~e)|_$tw#bx7T80mE!FRE3Sk4KgC!HoyZ8Ph%2am2{cL
+zZOPlp)zpo{1&faY+f6YFNdq%mHD6h_YAnjpI5U9JrCPN~JMZ~S&ClXP{4-Wxn&D`&
+zRlKH|SR=hfESDLALomZDev6N}`!HsIxhAyqf1$6a!h}@*tIGPl!z*jZS>6$d;}joo
+z+jct@ORM~5E=#4+uIWx>ZWmd9Yr!7N@u6{qjL;jbJM~9W+Iv)W3c|eu9K1UT<of(%
+z2YE16(8L>oaY%4*tmQK&Y@%1ZPACIwD}87<k9dN+@L#Ie#7;P%0gko_1>6K00AzBh
+zrPtv0gM`+s+`qAW=)$5b{flln-iBu2B&8=i5=n3F9ZAAk{nZa2EeEL`saFZO&gRmc
+zVA*dw@Zw0TeK>L?UW1}m?weBQ8Cz6{0!rZa2emx&ADBhSX~87$pxF>!L$lKbgWdsX
+zb^UxO#hW2_1Q*@ZGo(7_B}rI|?y?yf>VK>1Kvv6*h1UYxmg##8H(ht<;hI~7hF`au
+zUGY0ADP8%r?`Qywl=`jz;mz8Y)}>1=%*1lvRd6~|qvgazFfa_V+`j(&h+E=$9kUzU
+zG3t_fQPmmqv9jDFt`mNQNI$OV7xF?WUR8EPXDV#qLpWp&-JR7g&pemtn6TXoT?pGV
+zS0u92xk}$!95f!Xd9@9AwX)?tOFxJ<i}4_2SIq+CYN3#>f<SPgP1;BFe1#+^5V3I<
+zyXA49kWxp5x1VeUmz;93VUR+SY<tY$&t}e@hmrClmlWsth0p?geuJ<(v6icn2lcBt
+zAr^L$$}W#t4V3~u8XR0ckecGe49fjNb<&pQxjD2=#chYIm6~Mkre>^np(3900ejE&
+z;7qKH=y;LSN^<vDi+YUJu>#bUR6BCep6@!>Q^wWJ-W1e$5mf^m+b|TraNyPG4u0#-
+z>2Xk`PlCoTu!zr9on^)|kFjh_Q;s6LbZ#wKY09pEnBk_strvkOG+;Fm9#u;5UGgnH
+zqTRG@I*5{X)R_us=IV_0(0IZs=qJDQ)w^8eg$sx}*LZ(oZ0!m{XsEU6%{f(a-R`Ho
+zmBzntadX|$mJbj1w>~i=RA5ts^5y?JFCM8yHt5JlTFlpk3holqBfI|$(foUZzTWRI
+z2g}F4(bsw2#aR3-NZj4Xqi<Pd`a+lDG-1jFA3&}08*4<$G%UhDuxq-lRtIb3bVz}T
+z7Wmi%m{#EH2qQb0kCnFdkt9@AxG;0xj*BR3gPqg7umxqnr(_QAL;NnF<(_un%G4VB
+zY*#==zHzu?llW9>?|*)AWzc{}Z+1ynxm{G+6a*Icd;BX+*D$MFJGpvia97e3UB9Pn
+z5<fKS9+ADR*D$M^{`xxJpBUe!nvM9}O%`0e-{_5(zqJ{ICeyrMwOF@KpO25j-tTUE
+zNpZUsR6PU{oRyJ3I<xY>UnlN<+b5sO`<V8;E!yUO{k-ZOJ?_#!mg@PugW&(%zeeuX
+z{xqrom?QR_`DWX^j_!W{#LAOB<?@px)=42X3Nv2DAbitXXd5Fw89cUQF=X%dczj>K
+zSMrnhJz2P?KKAP#U9sAisy0Ga9hH#JhiOBR+Aas@Kn+rfTDs;6P5K>R?YCGI`*(KZ
+zN01eutJN(h?Ns|z*;DqsNa7Ztjnr64T$H6k4r{H$lk&7X^WY<et-7vAAL(!_oIMm2
+z+lK09=fOKK{teMj<N`&Z8qeVoh}u`_xp4|VjMTIx=E^-V`{kGj(a`B9h1Kv$Mc3x~
+ziMl_$9%iTieLT20jzs>remIz|o$2*_Cvwt-YBP?rXxh7M-92cYtbDA*r)x~Sx?Y~_
+z^^&i(98mt%aa+g@@RXT1tRm~bb{&eQz54|?2@gzr$>eR>96Mp!bne-2b$S=lqkx;j
+z;ho)Co1?bAebn{RRt@eZ47{Z!*I=UG$XM**WTb%rH@s1ljKFFF(Vlb7VtcHYwvQhv
+z9!niIa(>w>zh}F@f7;yzeQE?}g_&y}6;q&a8@x4nlDjI@;J$l#;`avNEf}0L*6R^J
+z9|Uv~(TOK}TkjC1OQD>vuWP$}^n=m<G9$3aD);2U;hG*_ykh*_5f96rHOv*X<ECpl
+zy?D-Kr`O{tJ-#|ksBW8jo%JPb|19RmN&E}tqI1LH!Q3yB)S2kNMey4N7qPVW<1C1N
+zZCjmeWxsU1<+8jYMWH<#ME9#rpVwl$=0MJmk;YA4aLiDWMyZWVU(Iw_z_%KR-2Gg9
+zil<o*fv{Vy{0!6!t(HAktvgDxQ>zP5#O(%<%A-3tt=pa$gaQPjGmI<Yf}TKkMhA7F
+zeGr%l3k63{%WQO8PG3P`t(T>J6S=t8v~=VUN)7&(Etz92sk1qT>v1(Y#<lOJCn`W=
+zElqV6OaNJ$Ee=|(XfcXpe;n5O{!d9T4==&MMk*f1`dzTR{2g!V+XF%`U}LV2SO;-X
+zwq>~pwI&*D$Dm{gK_PS-pvB9-ZJ~>_H=i$*S?Ng#eO>9M>5w7er+!az!0S=zTeP06
+zM^i9e!bKX0+;w>r<pB^ZpD-Lnt-Mzx4zngBb5}HCK^E|K57HY}_ndZUjV2*kn=%X{
+z3i5X0x;Xu=$!yvetcUu}R?KgG9xP}q!og55O`<2u$G5hI)14Qmd8o#^yFQ{E*P?eL
+zOIPv{#l^A|ug_toDv05UftBHW^n6>byGK|<jhH*81uy%-ae-6jI5}5Sm*KPbfhF?o
+z((i4c{)1vBiMGuoU?@}5^j&74dl_;I>Cm?L?x;W=Jdk)hz{2HbaD$N3JDJH0Ox=tI
+z9++EtJ5(Q8c&jR>vv3(3YE0aSs7`9Mru9AmwF=}O3>;+};2wW#D!P&YG`{Mc&v(um
+zKC>owxHaMFn0R}!TKXM%GxtQ}KaNf|m9l@9s`12FR8c3|){0|Y=r4-%IZHd>XFkKe
+z`38cY&SE{3k&lAca~jW$2fJH&EqFU#q+6=}?hlayEr=nf*ROhEHpMcA|KXxMUw-@0
+zNR;k!OGJR*Y55s~m3ixtycAiG!%O86<a6ux#d76sH>W?|B7HM;M)Pe-nNnNZz9l3e
+zjCxUY^K>?HOj}RTcm?E&J7QzRKf!JORZorr;n{$0PNsAXe^b{E#l`3U`FMk|S2|Yn
+zhF1g-{QmRsJ$PP3YGu6lxo>N2pJC_z=%$+<p@QCj7z(Muxb;HV!AJBoZ=6k1<GLo@
+zoXITz{hE)H-IOKV4gLGxp?JsfSldn~>K)LN#&lV~-j52+cYO)d!NjYt;!C%2iSy~i
+zOCQ`>l{CIf6b9(;2v%KWh)nX!>X_$<%6nZ*Jj1$Zm|g{8jOgr(?>Jb+Spphh^*OkE
+zr!_p3$PJUo*L&d0!T)`~+nQ-FZOqNJBtYTb4N#G5yp6XeM#1U>9uBsNvJ=}U3%2JY
+z(Q!I8fprm$4}{E8V6lr`U6tEItI_<UgYw_Dx`z?{36vk-5tvN>&M6b1veB(~#FE2k
+zNqe=OX|fyEK1|IAXx&?fUXAlrqU$})&0Ehd_F}*v^Suk<(fq%i!nY7Qvl1)w|K?b;
+zc|Xq2XJgNZJ0IY^<hPo{px#_KunZy@0TNGc!kUP?I3H$Xx%|&@Z{U)pd_RuzpzA$;
+z3D!AD8B=l_(~ZGR*AwMSg?zr|WK_L!Z-g87v1*WNc)I&zhg{2K_8S81OFJf7-XQCv
+z=|#YOLRTET82jmI6P(Y5@=nCn6&zx*u5)UjeFuucVz=UkB}>MYfn)!EU^7e~;(U*2
+zZlvkZIGjXaVd*;8eWP7myL&|9)XaXKkDETv{wP9vKbMw1nP+^x;m-wesh!kY&cm`s
+zkO5ghqM>FLM*uJDu5lacg{3Te7lwZP#RyXGa)s~OcmxA@?(NUC-i5%7@rK|g*u8vj
+zjQjlk&thfQziQ<>bY)c!Dgb~m9{>RSf3RRq29B2hTdZvQ|BIE?+?M|?^p@RkYBH$0
+z0_||QTOY?tn@-6_*cR&mGEsX2j0FiCjVMaR<G7S%g4fpF#|-phqt3|ZP!S^2nJmuR
+zRI$SO;ZAGw<r>P5i85y;*N&>{i<Y-yR*UrB#TTFQmE;!HZ%{R!v<{Z%jk1A0q;1w}
+zm2TGHi;PHaC2Th%Wox*Kz-+c&jDAA&%$D{3lkY#to!*Nsb?aC6pTS<CO0Oo{?X}zX
+zwpCV>`$Glh+j3fb9q3`T?pIrxr(Y_%H_n#PyU#kFtG6=SvMQ?=8>)Us1+~zNnqA~L
+zDmDlXKPSGw9^UzTv3^O}=A9+wRltwp?Rv_x-lkTr22_@Otu0Y#)YggJ=;-pb%UXS<
+zRkFny%bsnW7SnhN`Jt{-zy;pU%9T>9uNQ)9-G9~tRVI&TWvx>tC4KH6FNY&<^?Y70
+zV{ezV^^K4Lrl*8PjK7=)%!tVEeox2N8%<Xxcru)K&rv0SAK=w>T~y8UbA-r}Gaafs
+z8_FL+E|i{co+DRRCtM)#NXD?7ef_fgE~9U#0UE&&s%uK@F2K|mS?j9&jh(pv^<rC9
+zv=*`aOD}<rguElej>a)NDl!6rOJ7Q|H4JI<ifYcVZ^Dc^to=sd?Fy`2>>i{0fN)%u
+zj2}9+jXQ2LhK+XDkZeOZ7LYqZ<!q?DO>u$vdwpKNo4GKITRQ+q&>~gzR0a4t)sM84
+zuFt{RJ(i=ZH(qJ|STcJimYu~A{4nZDt0ryg$EwQNlsuw*D@!;+Y@2t|IUl%q7F5dj
+z3m&bIazK;7BQv{sV$`6iPv}`8Y(Y4>FTZp)#cl6Zf%gbl2FatZL(YJfytP)w?TI2~
+zru1OyY+<)V{2yoIsW_2<hp>Jl*}j$n>EdxIGoDcs!p5pqO{)7L%3D9JHTbC*{<-R}
+z#`#Yb=iCCVfc0lm#8ywGG-v>(^?rdjGNqk0(_xSnH1s2P_B@z3{f@J?2ke15^2^X<
+z-1jIXl|V{=64QfjU8Dkh*0AEl>(ug$HcM&&o>|jj-LM3az$##|L=-6N9LiEJODY7i
+z{r7>j!~e~TQg{<LfZQG_eRMQIyDn|vV&wy#JVA*bOI6V*bYlxZ1VAF3jh9qafMSeR
+zXa_!pzOq5*f;YJcMS;oj!PUe|(dm+VC(x0DW+#eJ=k0~Y&S&QY;#54N;pAy1i^75#
+z+vTHChW`%CWNs#I5*45IGS_w@VRQ>ff{Syi%@!E?B98>Jk{AOXu031RlHmi+tWo4L
+ztX`&WW$@|EW!Q=y#O|Erfy8x(qbSHlkbo-48+{QqXtY=k8;KGGV1#4$@jn&EM+Gkj
+z;!=IyT^$-F1v1<^rRHxO-DWn-9Be0!Bkazvo^39Ln#ym);`e;LMS}}$X2sl}yaAnE
+z%GvIP0a(b`9^jt1nii=9%0#rZb0e+iOuh4ZOV?8GjRT2k`>wAR)u6$oS75`0u^~W@
+zrCWfCSprL=U6$DOU~S#yEO!e%ri#xssA1bl8X`;!Lr2b~HdYdkJAk=Fv>cVe<%Q@o
+z05W%igGfC+3-m3G`2_+IoV?;etmC4(sjg|YKnc}*IS7HUt;9gQUDF`WRll7QdO7&&
+zaIc|RPs{1<;fq=62cl!=lf_d%Qpf_7`Wou>`8YICd;i5-HGvl-UHd^&H}0|BEcqt6
+znfjEdYT2-n2fZ)%0H*Qhm`rF$P$L*X8ng%mgsFq2K^ck@t?@B)t%vC{=Yl!(TNQ^`
+zjjmZQ=@O3(2MW1Cd>$!K5`<am<_vfg{8I@dMlFi$f5|{EK(E9e>QzS5#%s1I;*H2_
+z@FChNdjDwQ1PMrp<KZrupD$bU8>`#8_j1Dk`cDM^14bDQfF)q1&MqbN!-FQ+rDJZN
+z7}7-|+{Qj@Q%Z-QRusi(9oZ5p6Xrft1T53w5*g2Nrp9g@`Nz|fUBwju?+T@aB71%~
+z@ej~Ua8Wg#f1hTx|4tn2n$NJoTfsWb61Rk@gcweoGPhZ!ULs(~Oo0L35gm&pO}_E!
+zm1GVq<tHZi)3CNTrxB&_SJLZVbnYYmIN+{d=%XhhHbEl<K)5&nW5m`7JqR38jxclJ
+zSvAn(-mXqX>JZ{sgA2xv3MrQ+&`klw-KH!wW_H>FQLjXqSGn#nLaOkQPx}h%*c53R
+zAcR;<5vC^UF;=pEyGZjG*<|sCzo89O%&DQwj?h75d3as`_Ojem%_z9@tf)f5t_Yi|
+zpb73mE9nt_Ggjty4|mM)J-S^#QWY|JDvCOAPnzrDDFlLko;^HBXBV;+tCXE%8Tq>g
+z7rzR>pLPG11kNCgRUZrmljF;rdzKlrbo-*oT$XkFfMK07gV;*U{f%BeH!#wz({>_h
+z#iaOi@pYCPs9PT`VictqE~%hJJR-Qa$br8%*<RsFoh-7Ecv8^Mbf8cc=^p_@1Wcfq
+ziSnd4Zr?~vHo{$DZU@)=JX!#pK1p7n?w~|H9tty59vz0l(<X9q+HN>|{IXjQ%Lq`u
+zP*!$YEIVkaCl12Sk1&$9d#zHK;nN8ek)0hfH(H6M6B2t3Ww|lB!@&|LVC#fn_qh6T
+zl6zE7Q%P@|TfN*i1C4(g=7JM?Xqsy-FSPzJWlsh4quU7K$A=%|ljNWGVd<qL-k2l4
+zy;GVG0izvL>*Fw;4Y=+tRsOtH<uvU;d>Aqvn>QnrI9eH02@mA$I~TL<FYi(VVFsJr
+z8S}L*(e!C&8g51w(-A{qP6*)~s<1nvvod-sWQB?t0j@gUWZhlI^D`C$!^J%z{wXv8
+zqX||^G%^1^Xoju*u%SV*_AH#N2e$F&f-Uf0iVy+ah!MTQQro-CHS?AYrI>(t9Ab~S
+z4vqX)k|KXci1!yG(!47);vQW{IpHRhPn;?akv2?GR$)PiNu=Etzo8}jOh<T5EE3J#
+z%#2|21HPJ2Zj$y>TfWcHia&W|!+JUm&n7rKv9_eG;tp^PH;r@Mt!L9yrz>gX@G*9`
+za_}6IgSj6Y6-Q~v$$}-I0}u}`#ZVA1{-b7}bM*kG1r3!62Zt;HAqKZK?s-Umo7nsS
+zcIysW)jc&JTu@Yt%g5aq#hT<DnR*I$uj$ii5+=si9qt7LvhXn^3grVABZlT^6lE{*
+z2CSUdEGmAQvi+9M5b&MSS4QvPo1vKeU%o((otDb}MGS)@=W8AZIE(z8sr)zP7Ky&J
+z`~!RN$Kb7>ouUsRAbAt!B)&!c_o;>!6vVKQ=c|i3@D&Bwyy7jnZ_U~ytYpQTuNiX~
+zM#6GsoSmm;!$7J9t0-?$9LY`5Ca5(%Ev$SQmiZ(V{(=r{EOOcpor28PVudB95o+VY
+zAehw8^sXCt`Cw@WK6H+)TUJ-nkyHaXjHG$^^EMpoR2#IuD|1M~g5K+v0xtAkp6k}u
+zL86Mkp&`l8@xu^Zz=Ty=jy+Ct0V!ZebZc7t(YB`yG}M%WYd~uBOCE_q<%v4zq!22&
+z3S{B(jGF6p?~4A*?%Th4cExVpZg_Zf51v{{Jr{q#L7a`eJ>wERECn}E=waj#`rHC2
+z9W#Ry6$&*%JivHHAKyHQM<@w%c8~ja;HR6uz6$0K&VCB#>97IdKp+A3WF~ek>vztq
+ziI`ZOOzOx0oAcMqxqKkL3r2B+US{A>vK(IHq%=A4l_vk8veL2$OBc9lTcW@5o3oz&
+zHg5^;e8431$)2Go!l!JkF}kvjHWNS+Zp*O;v>+b|?sLZl^o_e%lwwOil^<*GYnkyb
+z?4Z>?GX$6<ITwI5p8mhY<&uCX#VPMLk#No=?;{~^j!=?IpG+0oQie)NOJ2Vu45X6~
+zSgP{oAWtKizw-dMENQ~>X^j`dbGjLDGtQ>_ULTy6`u<<rDBAKO(diZg?{I?@B=9Eq
+zw5Cxakh?z|QZ(`sFDx9c`k&>rOeziIOLi<Tg^hyM<KM|fx@{rcxs&#%*@TejL6{}i
+z4W>#z5s(U;@65W4M@age^mBHF*1YBGMcx~^PLav<RcAo(IzN|Otz1XnwHKi$IoK_V
+zWp9v24#q`R!VA&X3=GK&3<Eb+#|VJhL_-`4y7jucs#gKU`;pHhs~Ey~1m77AT`XsM
+z^AcN`hxz8vwcJRpdzMOJZisbuFKq&<*F}Af<tfrf{SNNgO%n+4G&6A`)qU)VrWSiU
+zlV9z(ZnTci)X(aLR4qPcQkvgj4uxVeH+aGe3z{!D$}mEGZs`MSNQP6V{5^`OVVbC;
+zftEV4<&}fuls<qP3sb<ar(2W;r62`vjOQYI+dD{%^%-*UYcX3TO~MKe-HX{w&v!rw
+zjo>zZLr9h9d$@7sTZS|$Hf$x%fNj&u(tgHxtVYFako#hMJyZbM+lak%sX(zXxhGO-
+z{;RtWTJaqzFxpCLB(n4ClgD9z(J9iU4YTzO)AhnEI7Is$0yEmspC(=0irv|NqqJyR
+z=aa3S<a3xX@Ji`2UlMZN0Pvu_5Tk$>zEr@`{FcCABqoLYAsh~otg2^&m&3l&lrunM
+zS>#09`7&u^_hPiZ71*p}(mP@b(fvv45h!qQ8=3xTC?Ft?p~}TgEK))oonE|IlzjzB
+zD#knmhAgl*XE7rjbxCL1TA~^CY&a2aq|w6>(_9K0!=n+QcmWulUG&B0WKkhw-yCm$
+zsSc;l8nTJWlg(<h8rxq0bZB0iJsA$ccl~sGulq;AqFjxZLns{CpnnkIolR&8>tl``
+z*fw!Z0StP$7dnm|A~bh1#%L&45D16{N!AON3Dl66lsS%XWdmhVOCl$LjDp6NWjS`G
+z=>hs}(5KHikRoUZL2+5c)T*u!>Gw_mx@g0s{ubAch4}ElSc{06vtqL`ejSU6wHGW;
+z=cfb>jU+|Us%F#@_0$CPJwr6~n?phlVhrxb3K$FhdCovUp7BB*@dtofpg@2a)PDdB
+zjPM}Q=V1vd2N<4e5&i?5_=;^AvAD5tL+x#pETqb41S)5@>l$BfCP^@6f}rOE5?zl1
+zf;f?YY49dO>lR4s(O|CJw1TYwg1$tMpIuwBeWqPzK5C5auW$sJU2cjmEn)z060=zw
+z%77J@klFb|K@<-VYt6;w=PnGs&c?R<c5ro@g-PFcSj`CRPQhvbKQp`jfQ}ej=1plo
+zOg+&m0CutNUMt@~5^eG|gBjZ<c`k3&bpi=)Bo}3YWzdJfEWZW|c7wWFZE>|-FWzrx
+zrYqB@KoHDVM8~5r{S910oVr6E>dyKgVZkPxd37}c=!vl2W38&cM^JCHod=sIlU&Z3
+zG-P%!%{3it+#~%Dl(L*KWbtec94+?zmC+PPZU!WU;c8ItDTP}eJ{WFMA-N8nqPQQ~
+z55sjI`0*}z#WgU8I8Tm*taB~~lZ**E4+IffBfGeH!C@8_VwX1Co1fjf#aTC=9dve6
+zpxv@=xB<F0>4ZPSC6T>`4QGL3yd)vuvEV|vdY_Jxu7qg8Q|!Q63t32h&*Tn8tpiCw
+zX*w!hIAICHo}pNP!yftirbyMF+cpuc+;Es&Q^sPPjGQG8mW8yn*jk55OLd#1QR>FI
+zoK|u%DBf<$O@i5A0Ttd=I*1_dg;E_R9v9*^|M<kz^R>E~cFTq6T)=~(p3>4jAr=VB
+z^LcpcBcewJ2<G}$K(s8m8<zYFkU}YyAa(%_wzm$Fpq`0#YIX?qbHS@Gs+o!Qpn6+6
+z4xF)H^dYKQ7Du4|Bd;e0oITHRQKvr!y%qY@jQ!w5n)r0pUv|3Z*^x%Zf1e8&QJr4M
+zi*>L~IAS_;NT~2jNyBko4**JEP%GlaKOY6`aY~-yYhNrq6&|0f=3^A|SDAlQuAetr
+z%)ON~=bGJao9APHWdn?toy2#^CJ!Lg0e@;oRb2grH+#NG8t}elD;eRlz*qAJzj?tP
+z2M2t<9yc9F7X@I_dDDG}suATX#D8```6Nb(0L~)~hfWYA^XeK7L(1k`@;&5`yNaYb
+zDpE>xN^R81iaTBW<w^oP@m%CL8j!XLH|8^72NtiM2@X%Hk=5fPJmJ|9j*3-fh2SvM
+zF%>CM^buoKKyRAyq}ihtwpSyL3-UCV@B_;x2!TnP(mCZ8hcdo|PMjcu%ff;h_=v-O
+zZukU*L}ay8GuD7u{<v9;3d<eIy8Yc}@2)#1BoWZ*V$43MCs}hk9;7u)irf?;C`U3|
+z48%5|QQASc3q1Tv**<yu(EU1|hc~$5>l7%~Hzz3a_a2i-fYPf9V*aq$!63B`51Q3<
+zKPQnh$5<#}HvHxsnx6b_#aAv>f;Q`CP(As;zn>xHjpCHa9yi(vCZ!!Ry1mn$M}@rb
+z)Tb_nD}g+|7+c|WH6fUh<W&g=-on&nn!KcI1$B-t^XsyH&(qt7gyUbz269lw2n@5a
+z|C0Q%FOu1?*}-q3i%8c|fkCp=pSO=$X^7?Sx+JWWDeG?Eckgp4yzb*p?G1}83K>W7
+zOT?fXLB>#59FLx8+t-<3{hQ_Z6s+>5lNa4!BnRNIb}m6NJA5hTiwK>^3=L_S0Z7ao
+zxxj~12x|i24``IyADfj`%DX35=%%?`WTeJ7keYQr^4@~J#7O*_pDH-Ddp9acMl<L2
+zd|Mdef*uO0dX)`%0iC|Bzan+#3VfbPo4juOR&&G@?BQUtkbNM=i@aoXV~@U!06cCI
+zgL65}RqDPazLT81bf@?O2<6f-F+^IFr1leU<5qul^1lXUm#FXw)*nL{FmjD{<}T|$
+z&voVw9(CN<>!?!>ZO04j!Z2=Oa;zpg-Mz$SW^sa$MN8@!03*?}z!OvP&I?nQ_+ScD
+zVYIRgrn=<h7gUgTiY`eo({nn{wlE!Dy@<96M2HPPzut4^pbb~8+nH3eW9`g${#1w*
+zA>_$1Z{7-a$l4=4h4e^n-5-BgRfOi_&9+=!DwmkJ!F;O*J0V)OJ^^!d-?+ggjm$Yy
+z+&a|^_L&=PKJ5W<MeK0Q6vp9OHrGsFXwvmqkuvl15DHymx2r7t^lA2|nM9!eDAcB@
+z6gN#2Y0C9-J@HF`2+{eYeCu+NxY3_LQ)Oie>79EjO}T;jMJC!12rjaEEJ5u`U5dV^
+zl2}iMgWNV7fOMSPz?H(X<RMv1t<K*)wn~x&JP1*kT;AAEH2!=+cPd?q(#u~XSxs&B
+z%!pmfO>0)UwCK-?4cfTpoO&e`018uf^({s=_5nm_{@(BP9dB>r13GwM(wI)$LcCm)
+zv|}<t+OoAp@du*;K(1QGVIpau>`qv5eT1KufM^0m<Z>jSM9(-U6$Xq0KzcO3EtaWo
+z-cnE!+{`zr2lZpSS-?(rJ22uMwOp^RVN++Yy8dJ>9i9SdOKWJbChqD>q;QxFYtbbt
+zl4C>SsIGqIVI|OW!Czi9rf|z-T*Jq<magktDUL@-HH4eQTqmsYs1<zuA8KR&KN(nI
+zDbgV7AGI+M1pol<KQgehleyLZ5F6Y7e-*e`-P)G874fG=uYW_L>Axs@2OwRdC0(>_
+zo4ak>ws)JmZQHhO+qP}&wr$&fedf-^;JlgnBkGT;sECZr6<KQ~zvLWU*P4~1jT}yS
+z(Vp388-2w>2?<DE!<DF{uotqYffo4o$RT)EegYLar%QGo+nr>Z=4M(LUhN{Uf_%Ve
+z)PQcPse;~2=wh~)vR>j<ZhkMkbgr0eemD1erntZvGk+Mz`?PG{RkU}fE`uA#+Q$7E
+zcKtdu);=uIuqxMLqf%qz<L%=`Rz0}NmKNus@$!*jX;UFu5|c5Uh_`ny<%fR2)2a2+
+zuv(cPg-^7cu2L1Tx@S*%v+O;!JXdiCZoz7vF@%0g!V}wlmi*y*e>R_C^lfjjsU2L=
+zSNCAfkdYN(QFSR`>x!-?hR!Q4tUTKumMr|VBjT$plT?Z|64RKJREv#R*z=qZE6Gg`
+zY2huVG4Kt3i~omL5gQ<5zrud$Koe^9V>85O<LlBGl_#?$E^8S3vz!t~tE5-|;s9hI
+zc?Vv_vgwZ$4TiSPM7;fNw(~2Dl%Gck$lni&tEr=L?iG)8I%8iUZYU#R))>FPDOGr5
+z`O|1Qcs;#fPvZz$V47055H<St+;iRGA;Xhz&eyA}>L1?>_7F3n`JvYlf|W7-vG6e3
+zpv@P9ExMSP1YNd`R*jDR;KNJD$ePiD`_kNT<&J9#8}ql46QAwzd$$UPF>Yqqz`FOZ
+z{#}M_(?gq=F)b;3U7cj|4uWyj?dlfF;paU)0ew86Km8KVWXk=wNy^SEaRsc^cfSFl
+z3=vItnBoWIZo4*=$D5?~8+M{ocn+h(9Z`F%65}*tv>!e6s)3!zKFtf5F*5)m<KK~$
+zd}fHFc!9e4t!rQ)abRF@f6HP6FhxR8V98~O?z?SaY3=M&Ym7PQS>{gkrF3N7@l=3}
+zYQE26=GLHSKKd%vRY6zzmi+mSkUWWC)|c_rSk&YqKR_E)Z3Yx7KrExHR0K(^Xja-O
+zC=-=_l4&y_MagD{K^Iy@n+<#%Vzj0ZWJharg?+lJis69eI)}6ofl|iD(2W{FQaJVy
+ziuSfjEI6Z+?%2nndW#JU5X!`jY`fd}h!Zfd5V^$y72xqt(M{NhXna!8;_EBYOesAF
+zFO1mx%QC1U76OZD-u8DVml4=6YGT(>k7F79u&gBoR5pvT$eL2uea9WkPGI6Ld-XTU
+zDGWEzu}iatw3MabTJu(-Dj27!<hbmIR=rKwWf^Md1r}7^l1wi4F}0@(>$jn&;&Pp-
+z%`6fzaF?sSBurJ25A@r&te0$OYsb3@^!G@*WRpAz`W6z0eJ(U1>W8oLNtz{DbeRm{
+zv?yiPOM-m&6v5|U{6y)UH_moH<Rx5gGB0hd*f|T@O+S!Gwxv(k)7CR_uvO`-0*<)S
+z(qurak?D#H`-S*ulhumPi!TBleo?Fd|CZ(&vd7GRKbWFtKk}tP!^#*q)){dHX19Q2
+zcrxn8^U|;1D47z-K}fu=2-E@3Cki8`xXRPn=uYfMC#27yc=B9#{6eF76q*3E=5jJd
+zTpVh)iNT>A*vvpp><Vd1=fsBa3KasH8q<i)u6f-2?tnA#sEN`*mCKnp{YwCa^vNyX
+z9f37NYZ}HxE)0y^M3CYT!2UDXxD6b?EvZR%mhh^u2X&=m7*?l;I(~AmxbodQGiyOR
+zWbWlqW?kVlM9#-3qY9Jr?XQGLX`4HFgc%5*W~kW<ePzhtW54Or#5{|ktgIDS-_mbc
+z+m=I;hy^QZT$<zHKdTknFx!CVENNsz6s1t&^Q!HC<LEvC5*I>w=Yt1w#GuZtvnz}E
+zyKY~Uu?%NWp0e9ymW+}J2jX&4j51*<o2OzJZ?-^WBzU5W9aiR~-aqUJ_s$6c_{orW
+zbVVWok?$7l<-x(iW**Fi1&vRvQE0&cyG)F$k`ISVF-5Ul?5*ilx`pM=3)swaw7rC8
+z_W7*SxZR`mxrdIv=5)W`CpuU5RU|P|R;pmW-5677cee70w>%a&I#m=<?w9t4>z~lO
+zy46|4oqk+nNt}BH0t=~|&u?C+W&)|*#D*lQ0R6&uAk9s7>%BTJIoS}hV(_L(OuB1I
+z9vO@};2#3QjV}8cKhkBjzD{8j#XXO5eB3W0Gyw653S{ecGDUcecu{x$oD%_$cAJcY
+zoXK?&E#KF%aDxJ>FvmL%x&FYNrb{gvV8S>v8b6X66i2Z&IyaeE;uH6kp8EYP%;GLF
+z9tb1!`@(E1+mXbT7*oC5VWV3#g?pH4c8Y~mQ@W}ULyyEOwL1_ox313h6L699+-x|Q
+zPz$(*lxfr_>S>|C8E5zI<XI*vJ0hk}a4wbBs>5~x2`CD;%HV830;Lu7Sx4-cbYeMH
+zpxXu0Bh)KF)A-CGQ+NYs?#7pC%?+quJVuFCZ*thM!r6HO7AnhPxwk1m>Go*Qo2@QI
+zm332Bt*1Eqf@k~lueEe1@@(MhUd#2@&ezmdQ&At7k4Kn^2{QJrWNM#6cJ<UMNZ2%8
+z5B13DH}^;oCg9&5-!D%g%_=l#VZDnQdvW*cL9??F7O*6-4(yL6qgD2i@~v`)EwtBD
+zc({`)xIUFSmZ#=yR-3O5?=Ak$PbY)TnXw4FaR$7*OnuTPFdg`My5uKSf9+m_WCkx=
+zPYm5R9%QMKy1S`>^I$PeadK*&8mSErGGR~)*KOu2v>vrkvT51bQPDOL<WvkDV$U9M
+zp}KmjJ2Oum<qZfQvNpbFJ=kS@>BJM@aBxM%rhuShEZjQh#CbX1C=X9Wa-o_8JMEZ#
+ztF=IpZK$4rR+T5+>=0OcPY)M|F&TE5QLd&<y=E(zX+OKsByN;O31OB~sA>x4sz)_?
+zL{}JmO<|UpP1ZKX@?M5AhuiX9ilfvB9cM97T|=uWNt>U+pI0>{ZhN;}MegiOGVXGB
+z(^Mtt9U2_vf7aK;AkVzX)RC>`DmNBdH9N?tUqAhGKEeNy+J*x7$4@*+LgHU;{`K$w
+zv1y!*tPO4K>6Dcq0f5C--&D2LWYnBop#cCvo&W&=Kz<A$fPYR0^Z(jL|4K(k$IRNy
+zQAg*0n@Rs5xuW_%Ac_7TNE%x@nHicny8V|t{)1PDe~~p?GS$xP=V|cgBK+qB|C`K8
+zQH@V4&rHZj{~eu_QXZY88Kt9?pMjv2pq6+Hf;b~b32^0vA`uC58%iTddv)W~f_m{Y
+z(N@=&H4b<1*48j~H<Kq+LzZR^bamK}LXJ@QxRdhtBSk4ODlR@DZ#_geDj`hE8yf=s
+zmxcbnLJZs~z?uFC5b|?T{Bt4n^$aXr^z02C3~a1y^&J0;g`fCe+>=*RAMK4x1JAk}
+zm5B`fFnycmX7&o@+zf>^%$csD1;rm5pI#iBnVOK0nwU@?my-yJ3g(x;5I>jC9TYjZ
+zHy9sJ7+V<a@9xPh?&R6oPO54yYey-}G6?{x$(|4a{qICELRNtH0|Efx0098__0L6-
+zmJkw=RTTL@y7&*}`FFbbM`6l(ogSw9oFX(^qxWP9*G18?2+>$GxRQBdwk}C6zyLJ4
+z{s$0AY&9avaSja^0e?p9?w!EO9Xg0Pwqq%}k1_de{`>&WEAagZu-nVa9q5uvn0q1S
+zH?zU_a|~>xvNLBpLMC1x=*gv_OO0)6I>Q>R-=`tX3s0jL6RsuDtPoRo|ClTw_y;Yk
+zwc|b@mZ^M<p2@{e`JPuNbHdOOTsng1Io3hI-<IP7TmxhYn`<!a9p3!MjyG1jXZi@o
+zX3SJ<KXV(B*4CAGhrpj6-L_<QBwGWnOs=~dm0ugPn<%E9+Jd^`?r^ETo}{)9S?lvX
+zSq52uJNkln{r)08Ug03Zfc#yTCLclP6Q~{NjqS_3<~R@OXbY*#G0nSe&gUO3dLEgU
+z(9(UsXU;FoGU1G&%0Ts5WknLa^x%W}5}eiGbB;y|x{k!8X5so?|Epfj_RCASLKWK4
+zcYZ|Krf7z30D#Lw6XUEs4)Foagzy#N_NE$h=b%LW`a3nIv<K;TMbWA)4gb<_vqwUv
+zxIWyPTd$N}(exw@>|VGjsRqy{5N+$-5b6ey!D-2ecCex}>T)fpLBq78bLr&i+TJ#W
+zpO?1|q6!7RAT~-03&JW8S=BdV=!_}G8MQ^sBzCk(U~s6*qQ&Q#7Ntw=3Qhd4FlJKf
+zv@GSJf{90n%80<X{_v%$+|v@_w=C_;2k;8YR!yGTm}+I*$Plz3ocj@4T1E6q=??0|
+zvA6zw5pI$M5ylJ7PT(0y_@NK%UkXIZ5)2B;SW(j;istvc?Qu=wz8329g2@`FkOfYA
+zf$0*q)hMcH8Z4ge5Y00le<EB2M?t3Jpr;kRIq|gb@&0eU&_6cS^4c0GtRF}5iU0tB
+z;GZkDm7amAnYGb>@ohG#OU10SB6Pp2B4~mn1BppQWcypsg0JO=Jj`5J=|MC~ubV&6
+zlXGTR&3<oT#v_HmkI6>7)h>nK?{Rly&N+Ep73nKfn%^tA45%xaJH>iUgyYuma@)1x
+zm3Q+vsIds6+F*5C{6t4VErRxx2SNbRv1h|&?UbK!E1vzCP=JNWivHT@^lY(w)|I-o
+zR8+t_xlp^id2T)_be?sn9-BD%)1kN|f6CuAgT|}oVu*CV-JS0ht{E#+j&<aT!XCy|
+zB*Y5Vdpx!Nse0P$-7qKR1=<$X{B50j#ulmCYWuCZ9R|y#t*i6K;nSgiBOyWi@=D$A
+z^~6p#Ilg*Sq0G@cEHX~s73<Uq+JrAEZsGr;L^Tbh(_ffri$$u`!YJzl{^cHHdF|>{
+z&J|a7%rrg^B=4r~yjMouP>#tuDZX|q(98F(VdFPpQh~=eQoMajdKxKXx-)3#G>{xJ
+zp*~8@Y(Bv6p}k=!N#02QC>kaZV0Q_V#fm8L_2xmy@enWI?Ys-N(aa;@*;CSApCWV>
+zR^C(^7*ah>boyHFEO?yYo-a<a+)5tQK5D%HUMAr64eW|BTEMLyB76=z@wSQg`vW`K
+zrq_#hjC(kCTCTah0yWSTVam@C_3LMpIwa~JAY3Fpe5u70QKMD<AQtsswhLL>m0bAT
+z4rRfidqg&8Jnh9SX!5s#t-R8?MOIYIur`r84t%x@fX2=bjP*tB;*iSb21cx`_G>8}
+z$4JP0Mgy?@P;RG^QNY7}1Q96W7K$R|xk7k_c{?O2Vju2dKahf<(EQ6vV-w1mirIwf
+z?(TZc&Q?c2h;?`F>BJ2argXF>(%}0$bIE^B1B6c4^tB0iDF!!~xd;laqZ*j$5d;d(
+zGDMY%nVf$nCt$|E5aKIktmIbZq>Cas?!wIhfO6FF5_psL=Q{zwLWQ1293o!;nU^B|
+z5v7@4WxJr#sXJf+_*;{e{9&+-d$uof7KX3MhwobV#7N$Z409AFYU`>Z*L4~<uvW2B
+ztqL=jN@aBCHL#>T2B05z^_m98j~CQ~Q;;Wu1--;ty?yFCKyQEc{8v-Y06!|y(L^5}
+z;ias^;}vc1cQ<?p(8Ps*z*^+5hSzi8Kv}a0)q5zdJH=)5?*821zEW;iv(7jIpk8d~
+zJA?7)qWF*@)C<jTDWsW@Hc0HTU}{n7Tta)yxGhH0012eu(mW=RJcIL#VJ>1IxMx>p
+zOGM2Zo0~Gz@FniL!(=eowOye+9-Lut^pcF$M0rNgg$OyqPV)LWiOyixh%NYo3I_eG
+zMO|_99(_Uv%u`#R_)|{R_-c+E{@~apVB?fh`Pg=?>S=K|?55b83!>O3rQF+FDy4>d
+zbBpdqv1Ci^vX#XlC%U&7=+Ctz1*Dw$Uy6nr%H5l;_^9{g-q>sL{)lT87q7it;T9QJ
+ztFx$YQ%W;{M+hlt2k#pR3o=_$vLdflUs85LMStDttsmIfr`JC#pR4=wCM5X_BwrW`
+zIVBZ~?gP^JL`5{yadrz2BRcc3X%!OJ`r0*@d=s}Kne2d<lC;HXtTTll^A@^)S?>)(
+zE?seKkXb>M39vFKVg_ajh1!;W`?UWFjdtv3<9X-9_=5*w+VJi{L-*@p7!9-6iIOee
+zSpI}}0XCZsYwPMMvv6v?6iCzqNwMNM#V{R{i#&o!3>h>8%p805yFM+}T@>w1`y_?~
+z6x|1#7>WHuwD|BOhz%N<+^@R>&n<-MGD5(B!gIS0ojRglVvw(U6%EZ-dZJ_r`Q8$Q
+z=-yO*R^yL79}MGX0t7Js4`#v;2n>D_q*-Dw@vp(a+D!CBI)NrwDSr!I`kvgcZTH7t
+zaG(QuXzv%YJ^h%l15A<EQ=)d04))iX*0a=R-^pHS3SSO%d=PXLTaUKH^7P{jC%FqX
+za-JBQyVSE|w&<X|dO(GL9sPu`Yh_;BG&80#;~XV5sOc@g;?V%5-qv*#csf<PzUNN-
+zK<{_x%n#Y6Pjro%`{Xii((PllTD=3?aH9Qoh-A{5_KSu+dxl~-!vsKzW}?wO9ALeu
+zS4)SY6o3tK^Df@Yg;lGfxO8AFe8I>p{E!LM8c0j6oI#}jnlbRNwT?{n4aUP6(++OQ
+zkJH^wom8bEXB9K|(gz2MJIwdP?xy!M`Z$3d>y(h}-NXNh&38*}H~^wWQuMA#czTvv
+zjyp8lp%^9%KhCZbm(ka$k|QFg9;kKx2%Q}M!3ph$g9r%l^wlEOhQA}k$w8u7^3Q=?
+zVn#aMox^8$?R<m7ePPIG%>12YR1e>|9s^AEi-dt!krm$qBse%m7N1BbRw{k{e#lV*
+zhZ^y;6-i8oS!kyTv1S;%5;H?+h+tTB&oTahV&WlQip&AfktHkns-|&$>Z0rv=s@h3
+zu=fXCGCy2(Z;GPy$mPSV53H|n?)0AvgeSMql4H;=?crD1MEP*SR*rTuus}=Uh!CX%
+zV_s#m<r@KqV?SY#N*|gTEwbp-9eJIpaJ8)DQ`-3T+htPy8t~t)sDIZ<dYe@{6S+~z
+z*;29kA>2y=zEe2+{d9#uiM{VAW)Fi&i3%gcl=}tQe2mr=o5a+vBIWNeeO~xM<(fE?
+z$Vsq^=5?tjF&QXnMvGN1Wt*F9XCWo*TH1kAy^yQ8j@cf|kW7YtWl!&sQFx>sIEUqY
+zknE7r476=&^}OL28fz2BOe04e%tGo0Yj`+I+j)hUFVn#7!hb$>eThIy=xOz2V%IJ5
+zDAq8P`ip&nl=%7l|9D_%|H0_6G;%dFu(AFhcuR_(eUkrh_85Mw^Z#hX{BL^yO=rm|
+zq3<6CdhoS36cE=S1<bWJehA=ZUT>|oGd7q&+(}>*H6{`+8^+UBK_>H0L_S0<Z!G6P
+ztUV$xM1@IgEhch2UN6@hB5rr0<540~+2zr(aDI(S(>SJ_QMLqR`zA{I=8>{n9j7G*
+zb+m59PvIaesT{ez+7BETUg8&g3B|T`^sA@;btoFUFE4n#u3%5fZyz;7HpqGmGw^3`
+z-EiWbK6F8j#U}VfE#zCf&9W2nq}YZ~1sd+4Oj6L$RM8O*sl4K@k868<5cK|;{sy_o
+zHP#zW(qPAYKqN7l5TfHZrVe-LKcThop45dmR<?$ro1o_AbwLu8d<Kz*c4iG;CZul1
+z!U&v@Mh_}y)CZyKR%6$PBg<mDtekz~M+df>PkilBTD=e?@<554P<28uib$0!TT#sm
+zFVoz7oZA%JA8ajkmRK*JCYq=g_Afhb{S)R~**pf!?6oiZy8X7Vk(RxpoCV-kFys1A
+zpY|{GxQAKrTG8`2rCHMak`{U1xAu$lgS9Rz(?Zxq1YCQa;U1WBG1(I0S}u&E%usD^
+z=JiC*da#(yrlW!q=5DPGXtyw$NT=lXxqn1EcJgW2U^+GhP0^-?sR`>G49ve6lQc#T
+z8r&Zh^}YLKU$(pm?=VF4aOjs>Vp`G)nyn;wnQFd({;gGf_jlYD@<*A3U;zM#{;7iM
+zS(@oN=op$A{8v6}mCCyHx+sFzvZ{0uxCGz;JVb5?fKx0tF~u4oT`)n-P+sFq_{0^h
+zli94#^+nJDtpr;zGEnpB;ba<<3GD$;x44r&&`VkJ_+(^zR|eppFQ1tYPR(AiG87-A
+zRKSkJC|}@p+&=MEai_ouKq__vW$~f!Vg2A`+$Atx4qvXK!n@F-c(T!Z3^&$w$YPdG
+z^vqM8*T0oeZ%t26pvkqHci|bA&PDQxn4Nm&HK)@V`$JoxLLR5ZqD^PEjJ0s*o%eX|
+zPtk@cOy?n3tT)p=4YaLXuyHi6R-zmu3`ENwCO7;LqE8SqEd8M$&?t%+z(A!f^)L{q
+zTzVk%Iny2Q+q2B&bNEw$NlXjBB|$x8ivWX^_K3E8WpOCEc{MUdMm(1UJShWM&M%nS
+z?^`!clBWx$4s)L11`FUPU>D+51N98SnoJ=o+uiDM)igoX<C&PUT;MABAgMv@FT!9U
+zfwY83U~kOH(t)7cPkTLGr9l5mGpE*&8Qlw7?R@2N)qApC3Ai+huLn|x_Vg#)9jU3c
+zKS{v3NN@M%vO0@06W$=vLaqt0=KCt#q}v)QqAtxTd|jTiW$;<demE9k9pfVHYGfz6
+z2-aT5^5zuIR?Z9QH~Mx+vLz}gu<p6d&MdI{T1s!Ga^KpY9adJGy{R75M%!!6eM{-&
+zN#)3iLP~5>pevzhD}0Y8Mc@!o>s;aR+Uk{dgoW=*K$}dP4R%7v7IIfvMyndMZI*<$
+zj<FjbA1s8A)^L<SJUHAbu1hw{%1E&f81FOdD|HfmFrK7_Y!Nu-?axm?J@H2a4d`+i
+zY3(((xi+;_JS{*BG&qOoBEo(FZD9*xw=qDt9;VxG<Xy7|`LJl^S&*+M$@w3YK(+Hy
+zBAjPc+Ds5y6R_nj7@A__fr(DUzAzSKwaRjh&p?SVSyK9AJ}uzUCbguus=V(Mhe+B>
+zs$q*_R(6UUo$X{bAE^}l8jMkT)Cc`r)q!l!vvapP{_3Oj+Z1Pnz`q({p9~S&<kkym
+zTC6VRQeu-k$lNv6Ov-~K%%eqq(yp?%<-AP(y{Z5%Ylf9l*w<a+k#Mp~)S#roqev`o
+zpUq&ER1F5z3w)@Hug*r(CHdvC4VB{U1v{@64}vF;0&<<TA!;l7VxiVM4t2NztE3aJ
+z<YP}u=!5nUy?AXdd(supq%)f&wmV@&IG&dClt+T^85uQMpkoI9BC^r-#EWcDQ}6)w
+zlCWp)vUsD;TtaHB%OKn{o0V+8@vBE&^Le#3pB=HwL~6Im$JuJQXk~1i-h2hGOhi1@
+zxY0Gp88C3mt-^a{b3~f+yI_%%e4(K!C!BQ?JPyN*=mVzcbpL5l#)dEQJ^Fjq?0Hf<
+z>CtrV8~ESuGT?TPA2~m<G3lS9i|n88GLDY+X8KN!M*p3i#w$hus-GS~<e6v4Hv@{t
+zZ%1$@Nels!;d4>fa@?@|>4}umDtt!=2IhNu5_{hfq7hNg;|x^OqOj6Hy&5XKd^T3W
+zBs3bbXdGZ!$#LlBF(ELdOhR9Lnx^Q2Ks2TxtVDpUairGZi91Il)|Ii_ihKJ+?=T*(
+zc&2YM#uS_0uLnIDvU0R{fX-FK<<i~ogNUq(PrExW`2F79v4KtJ-^#z7l~Pmk6I3Gv
+z1pvVPr}F<dVroqaT|Y-Q!qzKFh_nPjtJSb`KFM0UAewS~{l5IqiS0`R7N3c4vrV+$
+zD~p(`$f9O)clFLy^mXcF3m=25)xu``e)&%GQQv_}{V~JIHHoo(O~4vSzcLFq-JHQt
+zY<WCvX8Sxc_WCQmJ7C|m?~(p(e`d|q{`$?>H7E-Y&H#wc+8u;%u!T?dVu!26!3r4w
+z$rKD)&Y8W{tR9ye_G{HiNjhdqm@k?C$>V$P4cydgyN#ZCI4>Z}n;z`J(=Vo6^rpjw
+zqw2c!kzLkM-(1|d*YfJqq19b3_c_7B3aQVRa|6PoADTX;(CayckkINmitES6Mi&f3
+z*Xx8PNZE`V{KTWNwO@UGa;^PD3E<tG-B8sCDtQNln=%Anz8G=1uv4{r=?AOYJSY{I
+z2wJrz@ja6qV=*QSctw+@#ZOvP*=1yloghS(1g2x+q>1Wxvm(aTvXe<r-Sxk<hu*7J
+z5VVMevQ<VE*A1+8HT!)6QY#(7Ibfd#0LV`Pi_$76WF<-(_rN^-*8m^A7Kw7|lrG1>
+z&lo$1dM44&LckD*vKpq?sSj1Dsk{)4zgaDqlZL%on3c#`AEXYg2r%nD|7F2w=BsaC
+zD^{GB4kSYQfKiA<0r?`cA~BxGO9ZGLi6U5V1CnuIk(EQw{I;W*wA~O>=a$5+<4cUV
+zF`-dKAC*}Vu07i#pqLdXS;3gVv^Yq^fbv0(yfE5qQG+<vZh(kE;J1>a%wy*ghS0zq
+zMA6xJF15|*Yp}jN^&xZmkfRZ1m4cvx6|m_jesE4NDY|2|U@im2FT$BbHFMfEj9Yo%
+zqkf`9ef1rM#4-qTL;lrVG`2>ng*?D*Q?^X@b5o9sP^CIGZO=N14dNXj*XCjT_XX*W
+zW!E=ZeM2)i8@uV1|0nXc|5KuorY56dfC~T+?*#BO#1IF-%-Y$;!ier)(-013uKzpO
+z_V%9_YE=HQN?2!&|0&e)OR$(w$iKO$Yrs=TTZl3_Q0usNj_;KH%QciQZaN4fLJ7jo
+zG`{%xd~OXu{8FBBt29pNdj%dP>QVq1FW$5LO+7%KNPupcR(L@XbpDe4D$$#!<rZJ0
+zPOXO2uwJFrHw}5e=i7Du{NA-i<(zHVZ06b%Z@B}%I+oF5<L&F?ZHVXk5z+$Z7a^4$
+zPJGn`bf%eIEkq#b3kj-un@HDu!TYE`7J#T(oY`PF_K}pbb&r?;>DXk1y=O8e=&l!;
+z?}oo&3~kF#r3yeCU*vhGARF%{NbG$ANC=I@r+KgDj^v>gj2LN$ubTO2l3u`9Xbp5F
+z;tRf~Utwk_M-a&l3~Ng;KO>fii0~?pmWxx&0kxcW1-vSO3$z2kj!X}i=sdV2fk1nk
+zGQY#RN(eP%KIo6ohn^NF>6gSC69+wfmqU-|FGh}~y8|;D3Jn<s^}d>`U!;=Mn_i+^
+zPa8ujoR+MPCDfAH>JOqM*7eor6~;1JE1gg7rof5-P7p2iAw16(qGA)rZI2b7{=^=K
+zUl$L2tgk3tXN^Qf7rx?eF(t5^Yf{5pj$(v>bb<ds*es!N*2t+L26<~XY0QRqtn;_n
+zfHkdd{dB5_6n!6hi7!K-jZa^P^j?nbA9E{<O_uMsSEt0Boj<wWj}PAT2Zwt$Wk(t?
+zJJNtw4$hwJ8H3lx9$ycSBgP(UE`V*dw<lK*56l_CS6*SA%KAw6G5h~Sl^o~fx<2XI
+zbEJLq0+q%jyt3?hcc#@dIeV~X$^GTi$(C8YJU+dgvV<Yj0=RiRc-u#L!_<kYIfi?H
+zcC!4SBd=X8`Kh(PfSLKrJAhG4PQumG_UXm>`Z%BI)!x>LA#@nRAnx+CYhTG3keBsC
+zu3GeB@8axYnZa%o04^E<GAZ(6qJ%+3KgZ-|`_<FlAu52Qr!8A9kKMmc_L$>7Wz3RW
+z-(-FaX7TO^hbL%2cnD}3wh!R=QEfMOZo&bWSnZ1&`#APO-_se=l*v#9nB_pWrbW8X
+z@Q+K{81-TGL_doLkXoiVp)I^>k_XS9p2?pDlpvNCfru+sit}Y~XOxgpx4b89+q9iM
+zZ>vu6LoS(Fr#N5sM|*e6tN5}Inw^%Id)ImEeS)~uEeOA^x_^qg2iqfvJZ{*Tpw9md
+z#jHthM~9ke=6>^Zh}8cJi*v1YzT@)~1fQRAgg^L}@%8N}IxD*#+0=V^>jN%ho2~iV
+z(SF36!zjRo=*b&`Qj-z?vutDsen7_aIvy-mwV0?Jo&ak5p4rB?-u#9rw9Jj=tK5Ao
+zheVkQrCc+xF2!2rQlCnGV#U_!Y2EUU?+5a9%>!*$r*nj;54%Xi)xV<Lco&QuHzNMU
+zdj)^rH&7|zBGX_Vhc<+ztbu*dsan7uC+5sm0?39?u0OLHH&mev1>+6;ldn^(hEM5k
+zf1BzFzDd-5HlYO-;}g`w1!zWrokKYf5@SI3Ci(M&$>Z61)LYjkDKJO@<wUqUxHX;s
+z_a9-Lf`{<{gvd9kWMBor_ZUUR?#*@}x<4G$?ZlV0+hCBO?y>APeLcQ(eV2rAOWeMP
+z#%58YA4NExjK&&Eo$AHwj5NM5-q<i9ZX`ZWucGzXwSP$}DfgLW<`^faW@g}X^Arwc
+zaTgy8Yrnp{=_-4ADs(})^?Uq)6KFf}Z3$d3bOSN<(DGP-IHuKq%TUov$=5-AV$Ks1
+z4?&HHpg<wsSoA6b*c0H667q(2vCz$sQL>jc$1QPI54OUTYaG!Ku*!o<mxCClxtw9y
+zs_s5jmJ!tuf!v~EU-SPmLc1z@UVie2=Bru>sfQkID`bsCY#1Ekt8}b|3l8OD-R7ZN
+z0YLAxGq4ksOcFuDg(c7;C!Hn2gP}2<sUeON72zYAVkhni#Y+Icp<hN_Ntw4!PHpG6
+z2Ksh8iKOm`!AB0rgkFL<;vv<;r=MCZ7p?agWg+lX{;kk0`^zC8zzNM-*Uygf3k7qw
+z4@Ak(zDi)Bb6@%lrnZlVwb#ll>PX=fKc|;!bkv{9NN&jRDpe#k5yJi|l`l)1%+5@K
+zNsSd8trW@Y<beE8V8tRr8GN+V7Nlkb*>Zu@Ks6z}SF$dO&xyTV1Y)2T%p9f+X{2$F
+z@~_G7VAPJG)rq7aP-uyJXSJ&WbfcAOKd@>$6Txy!*Vzg2I=Bi2MXS|6kCqjnvZ$s(
+z>)Q@y@idjV36)?OBTRo5nys)d{sz!j(UGxBYUT7iv49(%+lKw=d;Al0<$*s)g{nti
+z(2ummQ+l;*nqO^_DYl`EDn>6!F3#Kl8U_t0)N7@QLa!?~A8YL(bZEdLp7>aOVcOR4
+z>%_to?7-#>$f6eSj#oJcJtK?Z`o+{|Fq_M3Zn1m$GDUnbu-do;_E+d+>Uvltj_@Kr
+z#Mfeh0C34^IWG)wmDw{KqfEgTPi|(uOog(WOK?5+S0fmcP+3>+U|!SVadfzdn{-;x
+zv?{;z`B>7pfGZ0oJ;yMnCNKfDlzoxS3_Z5Q<WxFD4z44i02ff~^IlfhUoM;=VX3+^
+z^i;y=G8Ed$mi=`X8`%}cNMK*#C-U1JB<uJhyH!wYp2|E2=^#j0rdv;o@mF~(KoLah
+zs=VANYA+q(4#@DUKfJlDB|-A0P3^dtazW+N_XuQib=Jf5cRk-GkHJ4ax>X|wv;KBp
+zltG|){~5wScG<{G$~^OmdD(PLQ0aC<_k4&CEZqjTeoQidYGo1#W8ovqLv40AudQ~*
+zJ4PIfG7(;D3eS~$ixH^WoHB{%j=6EWMQsyGz1PE#8f=y9`PWzrEaacFAb+MqqXq7h
+z1Tk`h5YJzeB(lT<vXKqzluhxdh8&*6jnk(CpDgZy;J`wu!*v=hFyL_YiB07Q<%(cZ
+zuS21g9PlC-`?9&y;0GuwzckwDlxuHn6p>V7j)YGrX^uIhcN=637OfTJrN$ftcdfa<
+z`#P>u0g*XxZOZ8l)BRRM%zco@fSDE@WAX{I_IN*`W9_P+)d+xUkk<M@tpxd{6*sz3
+zv9Fj=8$pAHGSx#yT>g51DcDCgUi&m!ds_iM;;UwlHq0lh7c9W?pewLVkjJ60DRVwD
+zRJj+=P35Sk?j`uns8E*^>yW$ZLa!6xu5tK)M8l{ARNZmvb^I|%>}AXkBbM^Y8k{#p
+zkkuu>6V_fnZ+nGuUF!@^-&oQxAIl|+uL;WX)|{YLiMQB41ErPZC`((}Q_(TYMp8nt
+zzAS<#&s4#BSWcZpav+Jhd}mrS%h=H6)TRuC{K{B{5sUhmp!e@X>RR<5>L(0vzcDr^
+zH+@hhTbcv@t^!PkMwRSX@y8v`kS^DRqseFot^<|nB4aC2M6WWP(b)h1uPcl)xwL9u
+zEQGu%0R^g@_JN8eM;z)YXQiU;SVf=u7+TDa;(Ofl=oSi@=7?)I4cJ=(S^_A`q=G{C
+z@{+sH?&oAdV83lC08iTn$>3u%-AAbi<?HY<4kLb&fnZx@u2G9YFE0yPcg_i?YO?i2
+z<L-j+s%L*+lgkgCZ`Q9U759b=tJY&#_Nb?Pg>jHy3;2A+#_HGEg{zqXd6|dX-BV##
+z-M@yw{*lAf;2bwO9_L9s!}OC_JNOyfb4MudDs#N0d}Fe9wOFgdv%%@MJ&abZa`Roy
+zjZJHx?t@)zQTPgM)xFE<kn-@?t7DTEsj0u3M4O)otsBHEo_0CNAdou|O6@8|x(hx;
+z5lm+#eFWN))4?7SW0_l>|G4HT{T&t=nHea>(cb*W*&Qn#;(M`|e&UTh1P?}-6p<>8
+zOs3~$P+SL`-lC3)F4YXA!0#>xmNqP%#frEMicy!HVTlbOSPp4y_b@Xt@R!g^7;d^n
+z1boCxfc_F!qg?nREFi*sQvAYC=A7-nu5YEqRoX)H1WI*~`ya~^X?ZGj(0Zb6CTS&Q
+z*67hb*l+OWSq-#oXXRnL(~%~^_uG!M!tr-9<(Eiw*k;0ee6|!&t7Y64=nLV<DuXAX
+zsCRw}1jHHV6L5@d5MAVXsV;Fkr>tTvPIdbh+@OPE<coUL>9pj#C9N}{!&@fpmX({m
+zNhh3uW3*7GiCGJWRI^Ti<FQE5ZEp-0YU^Vpc%fR~7_{Whc710eH9HV2aYgxk<dj?{
+z>~JW--J>RKSIqda_>u}fNyMCz6Cn<j9BeF2o61tO5e+{W9$4Gvl*R{{=94&1PI`9E
+z=HGdi`FFb_qV3Z9R*`u3RIss)l>)&|Q$fBSeIof)$C62W#3UB=rR^cgP1di!o3EWW
+zFB_k!qwu544zY5lte7sz=A7#l^JZ|&VXyrUTx+%=&B$R2DO-e`{hXSe#M4M4gwe(%
+zi%LNoEHY}dm*v}Us~Sa=Pe27x+ZE8K=qM*PY*bU`*YEZ7=J|&dTtcoZKmk3V_VL}2
+z8WBNVj(I1ZKdI<ucRHlJa=l&VR67J8tHQ6Rv_)hjcjhS?D(D{7uH1RvG4)^-drS<o
+z$I;&D&rU16dvaaHHMb&_;7v1Ji0-ZMN48c~alDLxS_3kk883FvNzd$JNW?zFNUf=q
+z&)s<*Jr5f@law0TziOAM(VMXP-h|FiNqNS9%dzB+HT}6t?V?<3W}6Nehl`xnb}xc^
+z+OEo{_Li*SH$7g#MY(u)7(F<=y^vht8<KJ@Fi7<Sv-VcF{t`dpi>K#}Uo7TJRLyOV
+zIhfi@dB_|Rd(OK4JdpSDVRb<^+OyrF=B-zpa<y!s*ihRye%h@p{L?BS(IR#?zL5-n
+zI4KFezh!`murL#_<nG12Qi_0o#3=buu<F;ZiHFJ)y1DecyFt;NhQ@pKP}*@PxrSLX
+zF=2j8I9EG^2CYYCazD31p;}|~x?VSVIm#XKNUeI@K4n(^$Ks*GVW<e^A)~65GKZ$B
+zRjKJa{N2r+gO+TJEbFy?DBOd1<Su*Q6V~;NTf&&?exZ8y+#%7p!|K<ix3g5I*eZ>k
+z^O{wIR>)cvYcgvGO@(X9=FzjD3<B3B>mfpR;D>Q?#qD@=1=Vwl_Kp-Qw>2yOyvR8d
+zulRSts0hH8{s0&LO`O0D5`gDSM1lv<LDW%C0A8sq%rL{pvzGPTfx68Q-Z;<V)JWt<
+zaH2Ef-Z`ulq9HiQa1M0$1O9r+&$yz9fRoWM{z?jX@ywLH(h`CevpolbXdG-?J{uiE
+z2&<AFTl^LTgzR|c*7k2w3?0F?xqFsF?M7axdrJbtvsa{H^f!|<tuDvdZIIC7^9=m7
+z+dqLXD|ebOH|zMb5ItGH=?-(cKmeXCbq2o~U#EF|hIB%2If~Iyb;?D<Gw33e_>=6(
+zZ$}DL@N@5ou7*~RFsw(lD=Mx5=TC;do<ty8ovk;A&~(B!hPE?%nvJ;MjcU{1OL0{0
+zosH|P=5W(Tj>ZfuNHygjs<oD4RU1}ujAJ}iEaP9Wt-Y~&%|$EBpXJO1vW{lm12|53
+zH7*ccKgkh2K`}?`0A8YQrCisUR{guC&@~*JYM98Mh`IzAu5Clm0O6m(4Lot-<d>eT
+zV`d(lQt0_Ryz*Tk9xpfg-D{PfBJ=fpj#CMdX}5>Yc|CaKEErnh5T6>`fOgp%s9ri~
+zYJ}zjqNcMCo!=|?VmHkE57SIT){R1|wt4+Qvr0A+GTm6D3REwLhVQ3lS+#B+f%s<n
+zo^Pfs?m!o}Y&3}wCA2+Lg3_C{rES*m7INMVyqm~!R>K5%yP_}0)GOV?RXIMkCfz*o
+zuk`buK*{B3{2e_>%eP0r-_qH&V^%D%*tAddN~K6p{-D3qT*EOxIRkIcmyW5~v6t6~
+zJ%%=U{tQOkza_T#FfO2%G|-NkE{qcvgYuvEnjt$ecPhjAMPG!r2#Z^!a9#R96Z<ON
+zwP10}^EcXN-XaH84_~osxAJYyspr>+vQk@v03Vo@(O0;w*oBQ*&S%tYshH40$Y)VS
+z1l~~Vi#1#J=-yn?`BN1uGvi{g>Pj(I7?NT>^<nuRbbDtJKOMhP<5tQRQ1cPNQfEcV
+z`3<qL{s`tUT6~e)A_X^y-E`UvQ2-g&RaStG4QBt&8P-4rkqL|oM%vXj9@jpiZu;Fl
+z&1KgYI4_HLY7h6f8oj5=S5<4pBaFcMUoe8F_>;OT19$*c_N$C9+9+<&Z<0$|&JQ-?
+zHxbM3Bify~JB9nKwd*4bJAlhY8{?<YQ{ayr7SdvW5#SnY=7qgp4c5zEg()M@CD6dD
+zIXDb+sm_2-z*ts!X{@UE7PtMf&H}8fOG7R65cOEPPG#&)0%ShK7d!??hj=MSf0NiG
+zm;K^_63cLs=?8noDk<Px@K64tfFr@^=7*%oDwTVT6Osd)YM)gcIOSa7P`4VW<5W=v
+zzUSeaOFyh#HXnO^WtL&mtx`jH^f^?>IxW`3%vKf4@jAN?Y&O=nN7@n0C@RWCi4`g!
+zI6-bYdAGyQv)Q<-Woc}4tTjLWT_6lM?}s{wwR+L3a`@8{5&#Q9$*nSKam*x)x<!c8
+z++dzw4B;zq?q@Ru!1w-jwOdB91t`0NfGDr_6lvv1K5FePvlNIh7h%u=fTE1-g96o-
+zp(DTg5kuh9ueaK+$QpnKC+%f<skvZ|aQRTad4ylKj#-L%G687yTo}tar!Nbafiz?_
+zJt}~lzf$$eZpw+$!h-!S8=S{(Drlb$OiDr~kj*`e88k4pw?I8gJxzm8r(~ixi$Pus
+zdX_6ox5@beRTxzuyyjOp(hMCz+!SDiegrVcRPx^IrJs5=T$08K^Id`TSzQp}POJfg
+zwZykun$i#E<*7sdwOi#ipUh(a8#KG5r9Tkxs|d=0GE|yLCu<yS_zolnRLSH*po*y=
+zN8mdJ{xdItZK1tn)L1APd8cU#yo4WogY7M8lYM8}@g!FUoLRh_+;<IOr-{ZW_4k-(
+zV;D-9f#kviAcsr@ZRrCBJeuSoM`fY?hslvhDxvDR5^2?T5H0TV%Tx5H=lGU}4GPs3
+zg;x{r;qyluKqDpFCze=Ha%na9X-(_jgNYv`hSgc^&j+g8#?{8sJ=FL3LU`?RMtJUT
+zWp}$Bkyzb2&{SXFN%N+GgNNNH;N3z`WH(;C!kX{T>t&)~K3hL<6jQ7e=<W5?%ZLrh
+zMUh47x^j$w=8nF60e@oH+^_QtE-sW~Qg$`N2}EekmHNZw8*8<Ov5$R2Pjwi%0o((f
+z8qOEH7l^iy=EY=~h5F5Y6&a(|pCo<q6A*O*&m`7!{UG-ejc95~el2x=FPc11dW*?I
+zw2ooY+GDVk5yabM*mdWFc4-ho%>#^5w2(p}3k?jo@|A+ll5Nb@WdF+I`ox>rWuG?4
+z3)AzT^H$EL5CdKhuuCF8+;2tM3>M(FS*f+qF_D2&o->@z;e@(LL^pdTj3u=lUWje$
+z9Srar6kR8rXf0+d3<@yri^3hu(I6)<6@jISKr*Zs{>vlZzx0~u)x6PDQAj%I<KQWg
+zeiR8>xG~YkYUmk5(O!%at<0fQSZc*@FcfM~w(E)Gz~|Cvcf?V)<)c|eZAIkrdaA_M
+z?-YjZUA=zxs9@+?=CrIA`M5J2m1qlVXZv3O3w}M)W~td_<ctN{hoiw;BEel3Qd4GS
+zZ6yJM^Lyc91Ucmev2wwDEDs^Jv{$Kc%)hidC^g^5Dpy7R{6TMyQyw=bYt~iqk2l5!
+z{X7)0znZPP1oXgP@+@ufV-a=+gULUsxQr)|4Kt=Vg?(3jYywcWXXkd{J(A;tXp3sy
+zBX?$^cNm@a_K-8_$ztbA#<6Pylt-;?kYIJ}h2aGdxrD|fPNNpV$G2g2LN%G-;(F<k
+z+zIGS&^EFFpB4&?HS*#K7A89XeI87d94AaN1XVA56E3r$E>F($>u=yQU}C{O<my>?
+zMx=OF7`kK$<w9?~DdbgvuF!&p42FJbc+KuQM>6)vsA0b|iGMnudo~d@k)YjMIcrO1
+zI=|GwvfEGF_onLia<F$s`R&NYiCTUPv!FTv{>%4LbupVYcIeh8RVXZwM6dJguo(gd
+z%MR$7w-(fZ?3|DS#9g=r^pZK~m?j(f+cwwrtxzGcjn~PqizZ>XkbzxW@#t-k{YxDl
+zRM@{elpbFX$U7k)6WTFJa@o@D;EK8HzjcwVpn?U%;fNSedqZ>Jf7;YD3lA|V$RwT+
+z$E)5`%VLOrdk_K3x`ccoibv|AwqZj-Z*@cLET`KqI86C~<~-nl>Z~|Wy!re<eE;0o
+zzWHZM5f!gHTuJg*n-AuykigL*yp<P@_u=aiW`R#?{Q$xBd8~}Z2?08{bG;EKES2;p
+zGL8hkBKfsXTa`qF#Lgb4p$KWK%J_EX&~`INxaOywzvc#N-~>Eki~BE7ljPAetrnjd
+z7gJeGm7X+q%G*!$*6xh*D@n1Twa3X{hKosd=2RmuMiS0XLNL{7O&a7OVc_v1vDTa5
+z=HQ*kWv&G~G<avPC%N35emjuBrO4N<ilxk2d7WI3RM2KixM7XvmFk(ci$D?^mg<>p
+z|6+*O&sL|0orbF-Yh!NNH-^&RJdnv1IiEht8nyx7?V19MIV_`E;~kntV-KpIF2BP1
+zlMTQAv|Y2Ts+zL=6QDg>&hZh7Psy-D=*y#t%Qh{Z8>zrs#-p_XYU>d71LElxSQ9-4
+zovv+moHA12tER=K(aemad3ujrFCdLAPcG`sF}zm{zHm<FVg>-eAEzE-!?b=A5&{T-
+zU+;79XcJW}{@q>vd`VsCV?s}aTl@WMy}g+h>&TeBV8SsX)<Zd|-S;ijX%iLRGzdd2
+zIk{x|6#88IoX<!o&{FBie7Tsc-Ygq~dr-0D7x_r*`CV*A&5C6m;Dcc=$2O*U9q}+*
+za4Wdy_`RdzGaMgIZH};FJed*eae91lSk&)sT~}e>h~XrnOH8%$1NnV?$TqnbV)b%n
+z&GMe_uzU@|;8z+9wx_a%j4qDkU<kKE^8_bcywTqS`x7f(s+(iRf!&#%w`^Bbx<x@{
+zglAoCgARvgdGXxG5!`b6IuS)WXI6PP4z&Ij&rCfZh;p=<X7z4*p#m{424Hv__b^PS
+z7KVxhwfuXAv5{V75^ZGaNnBl76j<bun2JPN$RJIP`NeR%a&qi*q$U87;i(7Wuh=bT
+z$H3w)ZwOS-J~hBryIM$u^}&QmAqBDX2NM{Cr~x9KmqQc)qV?N%aVsM?s}zjx>@bHO
+z^8DbO(c<~Z;Kg>oxJptPk6fB~mIby`lF8gqnGnVO()eu|C1!4$t9sf8@w**Ji!^ir
+zG9g?+o=G|Br7VTQ>=Z}*NFCq~$t=gK#N6x*Q5K3#`IS6XZ`4l}WLiXKO$aJ`u8R7L
+z*&o&<nj|*VILZoF&GWSJN)F$4Yi&oSLm2p;r^P)L=3oZf$|E+d)Dga)C-VS2DEs3#
+z7fC`!F4?XV#2qBg1`lXKI;RSnSDgUL_w0hLoM}78Q|Ecr4W*euarja^X8Ku)s%t>`
+z@AI5uEFqY~W&ps4hE7*Q*ryW!6g#TZCgLh4s6b6UOEJ59KEJq@bd6N*2qJwcSRtm*
+z9yYn0?5e`%6{}C-a!qVqE*SD5!n-p^n-)WpCyJeCyENT!zB`viTH1~=^T+0oQ=2=>
+zprs|qC0@%;D2FRM6<E3Djf*Ciy5B5FV*seF7U|hleUwFS*R1ItT|UR7g=ZnEJTP*C
+zm$V>eVd>?MNU!XA@0#5poecE;I<+aBMv)6j;RSI<Nk`$!By???QD{6czHF6&72x0z
+zA)fDeoE(o%#EgrT^?A=`$+~X~(ok_Bp`3K{DV`698ttNG2Tgd13t(og5^U@9|311?
+zc{5Z|-zpRkoeQTYKRORf&_1FnxvniVnpvFc+TzY71LWD>Fx2wbgB+fgM}gK@o|HgC
+zNJwcHf|K1)<&v0Pc!P1xQ*K0y71%7J%5h5&=q=^&y&SqwPmK9u3Bw9LWzZOO8^M8F
+zIi}Pe3aO$A2MdVyj8O(Qye0V{k|doB7KmVsFX;E#sFckIQ&l=(d<2|W9qai=xx#<m
+zfA${}wr)Rj0~r4YHH+HED8^*q008u)004OZA^rQmsQqUZi|9Yu;D6UrbgA)w+AdD@
+zE_)de9u!b+2uv06P3J&1wZiccOvAJrq$Fr1E{8uKvoLvyCGRXcws4T}mXJ7v?7P!m
+z*$Z9jmz>4|mzs?0H<s4wj}stS*4|W<imjaDUGnQchs$XLPb(_Y(FLE0BZ@bivB}h(
+z$r|IFl8h|Js=c*3J>6K+)Vki?`gepRU!zmX7yF*5J5rnzDBAjY=RQlZ84z{s>zXcL
+z$GSx;ytl}{u~kK-NrTjw67J2-E73YL8(JE#ir&*X9!;jRXP;Rh82^5`KVH!|+(oK_
+zuAjA=+6(L})wkcy&>;PZ8JUyYInI#RX+-CH#x4s)*EztB+csw2a$FkUF(C`0^xuf8
+z6&MF3qpDUiQKC~7Z>Wj_ps4qpVCl@%e*;hC*5!&0Ebahs<DNI`EGYIUbC8}skJpxC
+z+p-3Msg82OM{(Lpq^C*1K(@w+zv}nrbgOFeZL0tRoYRN4$sM5y<XNE`1fHGiXKg?0
+z_x<&oaKO_O0RwY18yQxz=yz&>8qUxz;=r(gEwTCE5lGD(ZhhX13owLoRiB6rUgt`b
+zjk|jW*KvM{NuNOkyJS4bxcglOPnvq!Ol~XP4+`n3E07ARCIc#K!z<(3k7DpYWQ6%-
+zHF}p@Q9K5ma6VY@e50|%)ZoLR3TtKSIY-`l6MScqldCmK$<&+6+?*KA_wDf<s@l>j
+zpycl<9_lMm9??+rUS2sYxxkZqhl!QK&HiK7WqBc67HSDh)SLWzw3=ccnSC}(dizPi
+zZGByr7+Am7fQ)Q$)G?`C<McrZyhfCX@C|GM%o9HK)*(vn&eMlI?s=7TcX=e;E{!g3
+zA?Gt@GLSUH2bTk+uKi9r&|Y6qc3pJ(03TekiS5pU4ruJe-q{5)EXYI0P%%dSl2ItP
+z7SXNct_>h*R~tdbxb^Dw&gKyzh0nxOz1FN}l`Va_@2*2dKo$0GPE-t6>wGw}nCwlc
+zjA`{R>9@h%4~oj8b|HEmUV}2!jW8Z?rpBz|f0^lO>jpiVej?NtD;9cJE%;UY&eYyN
+zY#`Z+W6n<W_!UY&8ngz5xJBZ9UkX8dKPb(&O6zzau6beVf#ys;vi@ouCFw;?O%X>;
+z%qfsYyic`vUBY8hrQy+v6*j#K_~^^#>*=DK^kc@6=#nzdPLXrFL0-G%z^I=FGvUq`
+zldNOsh~gJWVS@rTDgnX^Ze{_qE!oG`+iX;N|J#ysu+IDu-;%f^s&BuNSnhdz2dotx
+z!h3Khj$YvTOWij7PHys;9boF6uZ(-_m<<0`d??5Emh6r#OE4z>MP3uH*8uOs&FP!s
+z0cZvWCh}w_?ulN|fZ<kL;6}N21)0>tZ$~5!P^6=C`3g=zPA{xA5nDH~t;6NiDtru8
+zg$~1`w~G>Lm<o-wPs(S=k0}uJ#vC^eBhD^^f6S`euK`Nmo@n;~L*<Cv+;##uLX5kO
+z;jS3yrS}J6VOY!6yRU*6MbaOqb!arHe@|NDhIN8X>wW`hgfW*R5MN|_wTB&5SWFYH
+zh$%6@pn1dEJ%5GSq=hk&#>697wD$(2uzyDQjAnyAj_ExKnRFu&1O)K^BkY}`Y>T=r
+z&9H6TI$_(66DMrjwr$(CZQHhOTNPPZ*{bqZ-u6GNwx8C^o^#CE))-&!y+NZ+XvH={
+zvgi(&b46D@kP|;-!z1a33(mg>wMsbD`M#@<XBh|!D?8e4mw*%aG)7!(@&RjJmJOp^
+zwtT_%m1?DL191oZ;twujzw?%2n-GNE5y!V^$d%2PTp+<Tq2U2-p4i%h{K@ugrVH0L
+z;6v;huA^jWnxPIm@SmEa6b|giY#<EEE_MiP2jteF9_&%6NYWyLTzRIz-E;{6#HO-m
+z1{j|`!z=rjsAlwU4(2)ADXgllLbmC^wu9VMdxXPQ7DMNjA}cc1oPI>yyKXk!R$IWW
+z@k04a1YwJ|91D`biq+uCd0GA@(rF|S-1Nc65pNn{vBF$uj9qr-0REi<##2ukX)We+
+zXyS-T^YkSVeqMMs0B)69JB7T*Z2SGJ+RXLh(gmS=;>s!*pg{eKR65A7RHN1!lQm|Q
+z+APJ+K+@e|@IP7p@O;I64o9OzzTin|wEN-%0`1_J)bA&d;!D(<fOQ$QLm_fjJ0_0i
+zN*i%Z?9$By94yn5bvyFRoqK0o1I25v{6nsv$V3!|7BxJ!zowgte8~ow(jKVSd+eb0
+zRevp(Ya$Ii^8^G!tzkr=Ofv)U>S;LBzEO<^8MLQM*E!S9BJ1!0l$FJ=${i;GOV4R=
+zQ)I4WkN!F&M;)m?3P8>N?Qd1%>K(zC-iY2N6&3@137p1-X=+-5%A*!FdQ5fjzYoIR
+za%-MLxP5*CTBLDytrP!%6X30{dK8oJ*^8DJks8a)@A(*vbun^KrNR*S>!-o$#Bc?o
+z#XJEX&s2UZ2G9A5&qT$e702O$X%Zv<g&i_j%51jz2CtGv0+Y6(4KI?_Jhc<MXyDtZ
+z_W1w7ul|SdahVtqh5Hxr=0N`M@8AC+NVah>vj1Ns*8h<6{vYn&B^4`+15t$UYn?hL
+zh-Qr}vmRW^;=l+FC?}|t%@8F2Rq+B<bhD#s<+i~2pY1jDD(9l48QJSyDqG93hx=d4
+zoJh6=_F@sl5knDc<C}7Om8D)2#JZf7OchUOt~A3=D#%f;_yZNqSlA=@*l(xqHz7>S
+z2r=D!YZ>oFc(o+|2g`Vr#_F&X{q2Zm_6&qhXVcY|92J)w0z}h1>3^f*I43mdcHA|2
+zqmdPzhgb3Sh8lNRQlBR~)1+&H_XtvkUM0x(eHFNf8U!jU_{%35Y$_`!_|?D3MHa~>
+zoSkrm-I+R&aE+Zvi4jenyTVPj*8&(9LxW>)9%M}R1SmR2K|b?TW|Bi>UWIM~H&Cxr
+zoVEzCCZm-YLUqPv!?Q~?3L(_q@Peq5)G!n>;zhRzU~>ILcj0^_O*-9um06BV2ds~s
+z;0@DjUVy*wId6SbB1BWsSh(S!Ix7FY`V#n&EVm0`)m^xzGK}^}FtX(zVywILC0KJo
+z%Q9O#GE`({6C^7+=9<eSMzg#&8h6+tj`-7=JCtl^_N-W<w)QZ%k;y*Zs7_Tc_2j0^
+z{MNxHgt#*;VsT{_m;!xTcJgIwl1MZm^a0|Anw+Pjp}lhkD(eR>?4q4syDja{x9bCA
+zFSjrIBlm&zw(_|vNXe<#MT<g3Y~y|+&hUsm@2ffz2YAeS1>8OV6AUInv%=fMSk(3E
+z6A{@9^a!N^VQQ_c?k5`h5{CGPc&3`p2ER$c^BYsqIrF}K==#htzu4C?${a9M>KX-L
+zT}YZZoGGlhE;lX>VL%>_znN3!x7(*L8WtyUjlW9G+Qyu`Xxs)Iqa+hzknT`!A=UNE
+zaPB;yb={IrmH4Ahv_eUwO@A7{l)o7IBU_9Gge5Gy*)my|AMbhdyKdxw$-_A3<*tKF
+z5j78AA)$e2i6M@7>DTIN7jg3<PIGw>!y!G`YD1?Cq^ZN~xCy30E?l3W>{N1PUj1f{
+z;2x87a2p(Sa_rzU%lkX48RH&>3kCM^;7)S1v6$RMN@G%AH@U-S<a%Y=$x3VvrV
+zq(Bx%PvQ~7+q-p`BZTO<FasJVY$K)JH~bS`UCqbwl3qFyCqT+oxZc`0Oz(Mnf|2Fu
+zAx|U7tKuQd-@qS-7(P?hn3?T5?h74h!y8BJ=fPW>a36=k_8mn?u$2w@?>HC3ZP8l?
+zHQPc>J-2%^&toT6(I%a@kPC_2_QQ%|=tV1}vOH9FyxcAHA`RQ=-!|Ez_Y2{l^RV>c
+zZxQ={)il;7?5wjc2L?bctjcdxYK$TAX;nsUekzuE)|sE8#2*d;v%k7Q<)op?eHld`
+zcIz_K)_;qIW|s4K5;F)7j&xvFO(LZ@K@azJCmHdO8OvgV-rfUNRvQBPKov<8KD!(&
+z$V2YUDvt-MJ>5nfdaal45IuRQ^C=t$-$cT5!Rq$E97>!1ZsDH@zlW0k|H<~@e|{AF
+zN~0Vc?VSu9ZT{1_bcJgXvoU=4@dY(pEpS$;Fm0_z;y)vbO(NnuOpgF=T2Trkr@2*E
+zES^^AshsfnGCj%LDi<C<E$Xjq>$qs|oZZ3H&>(e8BQc*Z9)C)eKH?*R{j8+KYSpFH
+ztb{9<|N7x*#D>W?L-cwoqqD-&_-A@r*{LZS<2(HRsknUOG_xg1X*j+5N?Oy%Y+~JW
+zN^!tRyLgImhFpG%OuoT_Oul2I_W06LtxyJeO;~KF=t(2_>tJT*E6O{@WN;Hq*9cvz
+zSKjH%rr8oOmcZq#T|9Z~3M}#dp=PV<soB&49zej*5|va!``9Wz5=*%f#O-r;rtEyA
+zK5${_mbx^Zw#6x-$ApJo2D)6*T6OGP(Uw6cl>pNqBU%CAm5df^*{;3QXrcwOl)K5z
+z3&X1;d|gAifg<Q<8P(Hh$-cvwls=(b?t!R_ZdGb2&-kjD?l%G+4VD|rWzbp`o1n-N
+zvB-#w0r0NW5MN!yx!nDbIz={QW=~XKj)ZJqH~jOq8!bUbdmDwO_hQAU0aUB8#Y~C@
+z$UM24m|(ux4YEA!Hvp4$Y;lcX<&ghe7iB1lfCi;RLdw?-*k|KZNvS>{aA?|KLpD?d
+z8{8XJCbX7}w_KQU^J!0LG2g)@BLJxrc<FGPDYC0;Quc!!z*~9OY4MBV53W8Lb-h%x
+zR*efpHGmfTXRI89YJ1NbZfbin<{SYp6I6nuXr^%JZFsCHn3BN+9xv_>@US{Kx0od~
+zE>Ap*yrM|5ZUu8AWtoMs7_!YdEwtqB>GD3`6!!5jOeP$1Itp@45q@Pkt@MFa*(?7I
+zI;H;L0C2TAPKr}TB_T}I%W75yv~d0zg<jLF{*L)VM&Vku@*DU^^y~l%K_XqIxbb;^
+z;lT|<ia1vRBZeT8NjjlP#_t2>#-`rxAaRgIUDGzPu^}H~f0^SG%t{jM)+bqT(Z9h)
+z(m8R{DReC61dF&^WjCS}hkz{O-0JOLpG}2Xe=t4>^{3>+4Tm|`-Sij%pzr%AKB{%*
+z-8&7<>}woG(1U^ulGIHiU!*kn7KU_u+Y0lu6TBX1fxmyJC<E3IpkzVILAc#!HW>a8
+z(Hf4Bs6&*02EhH;yVgK#PE>z98bVl-@2euR#b&ppD1a_n=dSi-Pq0s#!C}#!LSPwL
+z?wSGkmKuU@+tCP0S>K5DAFXmDM6ikmASSi@fs_yX^N1>DVF($HXYgBtsQt|qV8h2M
+z!4$70MmcnXZ9nb5oC_=LJAm&}Hw2#jYo?h`RewTFQ{U?zxriz@pJWAZ-SeXb$<?pT
+z5N(j>n7p($AFbNJB!03)Umb&+NMediyDV>XUQp;A=T7ed?Pt72>eddGE6L1ZEI_5_
+z<SQiE(8Z)t?$(7|O8j1MbQ|1i->h}_llP{L#<rnqGK!B{!wgnId|9ka-G~j2+-q>p
+zqf&O^xg`B9l#Lqc`iRN31=4#VNUn0-$5PA!7T>b4`8<_}S3(}JrDaOqQue+#9E!Y?
+z<7}Zb-x(atTAl;1gkz@%KIU}^Z3ohyv61&NR&qi^Pce7@tGWXw$odA4bR+j>?%&`m
+zqkqUeefCp$TxQOZ<G-l|_{JQ!*{V`}tp^nj?i&K28Cl8FZhp2&jr7O@=eiQY%milY
+z4bRQvqRU%#WsoLA`HzIl1_`5pkdz}@Uoc8>E!!IoY^BxTMWh5y0Eu8(TzEX~<*`rJ
+z74CfX=R5VcUVlYhd`mctzoDqcc?{GJ&Z*XiZ5f2DWGmi{?;r2)&m!GNzCSm0uu$`z
+zGT9gA=VLcibZ{t-s=^zs)Xv7_nWHMzyauGfQ&KPojJ}GLc#f`Sh$~GU!?C!sr<X31
+zidQ-E7*aNYm83e|6(gvly@9SN=C+Q%ljzhYmrG`J-$Pcj`p4n7tifp(n_W<=9oTar
+zW-V)Yniu)lZviEvoid2|)blu~)1jG#6v_EUj#RoRj!bOfihVrOfXyM2AXb!t7$A;_
+z%d{t%7M&=P7WlUBND&?%k;qc=wEn@s&=*KeZ8x;*5%XbC`Iq3TIv{)XKRb!F+qiy6
+zBEG(@rm=Z@E`wo4bea{IlV+qFi1iYCvF6=1<q&=>c=D0vr+Rg12{_bUK#axkb%$sF
+z0-_zgz0D@vj>PZ^;P=8YFN)-FYTWn;yIl!qy{%Y=Q6?*RdK4v8Eh2j{4d1CfCQOg*
+z<>7&gVjlKTw%9kS$TO!{EjeWCS@lS4uV`7`(Be$q3)hW;f7_27%O%5Rt`)14_mh{;
+zR&uTXV)R%8L1*&fB9<0sb9|+tE#X#(8Q7Esr;ZAFjD?3Jn<xPC4$Feewi;&;1=V4a
+zyJC<oo0q)FK>m%1IvaRMoB*AEeeF+Q&mRfu0e?Mst=k&fHTpJewrxi$lB&p<HA*@8
+z4Zs32!`8Vwa>8Ms1*ps}kmT4aX&54L=iH9C9soSe=6={qXonUzBuW?7QDxs(t7Igw
+z_#x;SGXwUF-eT*0Ow|X{E~8s8Rye$ZsEtI=-u7E{hn*&?&#G?RESi&K@fi#v;rO@0
+z^iaK<2Dytf$3-vv@JZ$FjH#HQ252APt5Vbte0)R|hs3St(|M?PXfCk*aasOhS6$6~
+zQ%x|#BHpf5TtsA>XYIB9)XPL~bB-JQ(CknDoh;z%(=w%o-6g3bQa}`vT+{t&&kfri
+zs3c#MGfXM|@|fWWr|{Q&#**kx=HT{Dp9nAjG*;KgXnLT8xMiL^Gj?HTB=OLFj=bp4
+z<FS$YZ$Lt~?2?xKeE9v4YkD1NHhP5oq>0ZwP#(oV%pu@-8O89=-{aMTi}>!d(83Zy
+zpGSc-6i@l!Za*e8yKd71TQaHvMYk4(r%-C-8E?T0_k_huuw{hY(saM{W$a!qMpNqx
+z_UIoL_qX@A=%S~Hx1*sH=!*$0R4szfcB&1Hs(}dr&E)I%JkdWUw>IC!TAz$D-j?Vw
+zUIaBcJIWjF_;Zn~;}cdB+K+W|wy>tnlkR)I75F6(9pwtDV@jkUy!bIX@48`c4uLkm
+z5sEoIw-F#C9}#au2ScjNmnp!uQy{g}Q?I1o9&h0E*xAQf-#pxYi}dVXa8(P=Zq#~L
+zdn`pA#5+{lw7?LC?g}a!uxQ~dCBX-D{RmVzf=n@Wl*=%JLR0>Ethf-vX`n!~oW2wg
+zV;!a;u6s`~_n(g_e?&*80w3rFsBlGv_2v#$S(e9qS#K6g1McQb{q96@rQw8$U*hXv
+z+($o}HP!jh9eF*Bhw-za^!gNmq6$vtm}V&9!=4$8nh4`568aa{B2XQ-2YLO-F;N^w
+z&_pH)q?GbmnvLty1?XBE0nrrcrnKXkSYU1=u*Ad*+mIH?`r8+K|70yNMl)}NNR+q>
+zps&}my2YVS*JcWH${{lplEqcHarOf-bBDyzRTCwAW)o8%nv^?X*)qWQwbN#ZxVR=}
+z7~|uzp^pR*e5v^}t0I|`&9P_{Ye1y9VY4B;Q^Ag@)cCaa{!<M*v2QQf5nrLfxv^I^
+zT(q8f_h2X5r@cy|_hwWUY6Qy{y71^8Ald8Mx#g`<!iHBGim49taTHk{|0^<$+Ma`L
+z`!1*1WqNc@Fz;n_nF*oVWTN@N^9E}w!2L;(6xU*DWlC5;HXlSic*hB8>5qA;*Jb>&
+z<{`Y>>Hui`Cv*P00f4Z?Fz4;omDK%_M5_Q+V7@WT`<}fyhtubPL%9zZj?XtYsw(Ex
+zE_C^)wtz$2TLZku<;On$_Q!=<wkDfNl(1*M)@#NIGXnb_epuoFpyZnt|B@q!6uDaH
+zIZ?{6?WSN+(zP!<sZ(082%$xCA5Emc)&23KTBRrYfhESSpnEX6nzn{piKaY<5X^t9
+zQkS2L{~faG!VSYpjhW8nlSX;$Ivxbx{V7`5_Fs!xLNmSXD=>(i|3F4go!qaxxA}F7
+z^d;+laz<0Wpn%gM@%zS$^dNw}Hr6Lb`eHf3!5md9ck{SZ@>`Ub@Y!`={knfR1CVj0
+z5ev;=ZFLPkdqGGi!9{P^jaXB7*Xv|*rpAV|)1AoWT}yNVPeXK}%vC@PRFF*qE}Pdu
+zba>Q6gNP@U$mT1KEMlHq5rowD9thy8qPyZUo@`vN+%lEY82l`CUyh_w5<m#DvX*R7
+zl-iqZJ%ksPh$M1w0?Dm{nSagiyS{S8E6d=s78eUg9xkDtH?l=8v$>e8#*_32qGf_p
+zs}J2#aZ58kv91jkHWiGS2T$2^q^exKkAK(hxvy0vc~T>&9P?Fjl>qQ=HWOMQA6o~M
+zYT>n^{C(k&HZkB8Oj~9<RQ%!e#jha;h!R>?l$x(%ODx2hDQ3As?(VklP$y1V;v(4F
+zz%xg??*-J7&@H>nT!gvzsmZ!UDDN@-N=+!^NItEYW4{QIOx|v3w_tg$9_xfPi%HK>
+zU<r2#nG|N&5KCmOgEynB+>QoUSrnp6(hN?5c2-?464KKD;PGac`K?)T7abtb4QOIQ
+zro=l4^4)s!P_>|q5ES2$xX-r`%_0css8(p&JOF_N6@{x&JDS2^C1AbF(KCmn8qUlv
+zV&Jg~f<{vtM4GFjJ)b622*DYs-UQMn7%1I_Zu15h{G|+d%(ZLsK{YxDN*SM}l@M%r
+z3vLpCeGG)Oa>kCi!B<I)+XyYGz<J~BbdIDt!4t@oUJMU51Zv=1toYC^`#VFeDON@~
+zrwI%$qbt0ylZdFO_|B6iP~NbDz};44lS;vK*UfH5#C<|B=Y$Os?Ij(&@Lt@uuawf_
+zlGaJHhKm5f(;MMs42O1M#{d`3UM1F-J5+g33;o1fwFj(FA~ZQqJ=vIr_>FRGP3$0&
+z*LcSs?(HZY>{VoV9x~A(TpirVZ{9N~<m+_9vlk?!Q>WZ-Zs*n`{v9x{Y-W#?Rlt6G
+zf<t;1@rmmnw<HEjrwX`XK%KXrgP~V%*AE?WoEv9X4SZe|X#UPsny;o<RcUTMAa$E-
+zlTJ7rSVJ;gXt*9j`046-Saxzm(HSA@|Glo*8Gj#NMq5N$)>2(QFyEW&4DS(Nll{F9
+zxf1pak#a7At?>K-30W69!%Zj04B}PSM^Ox}KQM5bQLbH5wT&c_<25YrIW0daj3v-^
+zJTvFWiRxt0c`j`^Bh^E`=#~3uYfPE7a+^g-sSVQNf>HrDAoX@^@b-zy-Ir!OQ`dzi
+z?yFn(FytE>f^V2K16kG1=kI4E1n^$x=dUlyOg_-p<SIt~t&U-P=onO{SG%R;w5+=9
+zO~Jy=1Re<JJ99f%nk=jo5JEA{12{=p0T#k+y|}Yat)mj~)F^k#7VBd8W<F%@em2Eo
+zaf3k(JUXl4mROZHk#XAGUQod3h>e}h0!(UzY{xyx#`l`Wkb2D#h`8m3Z$B+~2<Wwx
+zwu_gXfpMxZW`13H#=W|^Hhp?D908~iCSV+t%S{|8GD$Ivrm8oCu3g5=Zwc6&<Adt~
+zx<pVm23!tZ<5yt}kKO*Sk*<6#-#$?2$PE~ZJS!ru&<$uMZ(DX?dCJ;npTM~kHPww(
+zVai#@^H9#-mCzCCUWo%L5$lp!Cn&xv8%uE+|Ie3`UQGu)czZ+Fk=M;L%nK_i4XkjO
+z&yVaeWng2}<QH~o3Bvia;iqG}mLD=?SojZuKG37E|JL;7Htw3j=&o}=LLS1k@W1!%
+z8(*Fs1@SPRNJ4+eS%oDCZ}T2g&xC6f&c=OAA~YSWEKwMq1Mj?7*<6_sud;dVlR&3C
+z!u+uL{WFxec|!ylIQKt_L+z#zN_Rdmt$gL&pgg{;@Zv9HfWi$zrwfdSm;d+Z_WyOF
+zw6|Ki9Qak)ufqWRj#~e#*7pDDtNf3j|Iq2ZQkIbmq(|s_pmyI_Lh$h&(-yWF#^AwP
+zP{e4gYb>O#zNl%brTqdZAZuqpgrjHjTA22jq{+~e#u&h`n%ydACx)T}E#t4YZ=|x8
+zz-^zNjO}`cJkeSJgY>hQ`k<*OGDX;q3npA|6f{@VL@wh$E^Rd}T_&I{MbR`{OHvmH
+z=V_J#lZ7m>L8yd*MpB1_Y7Lf%4ugJ18j=!C6a-v(QkBA|_5}RsjoR8jWyVfb`AZy0
+zS%P)Y1f+a2N&;X8(@SaK!ksn}ReRWpfj6LuN;y1tcQd|lW`LNbQy8E5gqSpAT8ILt
+z^bMbQZK8MOzRohcn!R&L!Do&p%2`}D&jpr2-A9OTz=FJCODIVLiJqApxJEKgtq|qZ
+z+&C}CBynugXStTfE#J0)apa3HZ8XCK*$W$gFBTEBk~|mxtDG!bAh<O*hAs%p_t&T;
+zi!rAkSTRe=^2u@IcWT@xexJDC;mZUX;CZ3RWRQtT3hNXnvXOleUkuw4E}2ccK4fl?
+zqt@_&)^NG(Rcvi7BeHQ{mTr*|(#gU2X@ejuh8IO{ke*<q1<y(@1pb*HlrAy)luIly
+zGEXiG8+>2cNp<<(EeJYIBaJM6H%89ynE(H8LGV8}hM|*{?SGsBTq#V+4iX@AzfiVb
+zEi?1}?u<xnp4O2l!d2OoFchrZ>i5TaG<Axo6TET2W1rwZ4@QZM)0Cv-_8i=duyR@p
+z{+uX>w#H7m9A~r>=A_s_uvoz1x(kv<qg$p+xv_SiGB{A`pjg7``cv^fLC2Mc(iV*H
+z4xV@!sE6E3f53h&G?%f5&EJHQ5k06%A&V$x|GF>sUih09__7S`K92;~MPrvw!G|nF
+zp!}6?PM^>?4Mmb7E9{N8O9{-cUtm89PI^<7sQ2B7&zed|x*3)S@M=l$swVNRjiach
+zL+jRvCVf6&G)fjlSdxT{2ot5$tkqND2}L8v-{bGr388*HBN@VdKj`M;#q-^hH84OS
+zqV+dGdng<31fQ@YFYSzF)$T>FfF=+ktvVENsu^hNIhGGV)tbh#(JCzJQeI|Y39=F+
+z8su@1t4`avI6VJCY;)GX8eB)KL!)jdwEq&T%u?hkW0fwiLOh56-&gQmd9^bY>$hE6
+z_J49_{hupnWM$^)_@9@M$IN9zG=AradVQISfL2n-%!M_2xn8WUth3X^qciz|u_GQ2
+zI4(961cVvHR3pLA=R^4^h_@W@uS(|NYcBAXmX?-_3cjj_Nr9WX(wILf{+|R1rTRm!
+z_<@>znl{M?Xyqv+XKh2ZJHVd~)xih1&Q%Q!6Qd^aeU6Qd4HL1O?Pxe{E8H$F*N<qK
+zh%tLO0=%-7f|WnH?e0_9ov$AcH`-C!Jqa@R`ss@|!d~Cez&_r$_q((oK}lnLDH6OA
+zyEAT{Zjh%0Xy>FgobNX`qBS`&Ftst~DL~bh;}<=gt`El>D?6j-Jlmw-r+o7wtEN`m
+z(G8Z|#vhmL@rU7H(qA0vG~ptS?aj{J6vs4uKdPKPG-#+FIRPqv-0EnM3zLFkmY2!Y
+zG?OO-7ZzmRJuG0drAqUt@MQLBv~dpYoRx}=YS=#z*~u<s5*%3ZO5D_=M!Fcsrok%@
+zm%wS#bbfJgRvlxd^ddLLUD!orVw;hZ*8|3g1y6#VOrxPg^0#o)zd(o$hMzM=@}vq&
+z#_BXP=PAO}aEkho@I>vjw;<=?e3ydJ+=+KP&T)7MC>K3fRM)$?#UJ73x{+Ef^xN_>
+zH14OO%NpfrA`DxT?%2Y!qEm6j>YT10=mi4-H`?9UJzTHGpT}3Hw;Mi@(YaqUSg(xk
+z2z5N)7P_64`w?9guGv=T5fOdYwyhoH7G>r=)u2N$*r9+KgKlvpz3X2qpb+>{50s^T
+zpC>|`RmS@^f||**aF6oKBG1t?0H<=FL0DM47niuJlxFecYIT#vmS{b4t#9e%OO>g~
+zmm%;GjgsEFcDzO@B5K7ZYp5T4+#1OZ_|3R+D6!9^;td7za1?o<es*;Yfv7hEM+z87
+zTpjf^dBBYFSI?OGHhzg@?6hhyJj1BFIC91mYxwi#WZFPI5f1X|_2s^MM+-2QO8?Rs
+z`>*cp|IwmLoJEDPff74QqU@@2q;JAT4z=#7be2jh=KMj2ZrtBYw38$X8uHN%O9|sL
+z*Sr6e6+zBH8yyL|6c(4%EE1V!G>tujW8tAgANy!jO|GxzGJZ=w)k#Ev!13P=+s0Ad
+zG=yM&UYjM7j8hpCt9cNSS3C%#2p|iwQ|3IDaH~01pEqjq1*!oeJ199EuQ1W)j6lBw
+zF{GufaQsIE8kecd?pve@RbrWceu)XYmV69VDjQC-)*K<=st^nDtOq8ktUa7?h^a=H
+zQYlz`UOkx}`6NB;@ORg$W%h_S0Do7zpsVy}60heGSm@)ba5VX0(?^FG(&z?lA`l7g
+zQY#tj)r$|<0cbqjaY0}T2qVpZeD9V&pxzNkXsHMQ?ovmIy}GJZ`rZymONLSz7cIg~
+zQ^}N}SOT>qjaqxXTF~N)?TDrjb?I90+eU>4n2T{)vaSMp!C~xAwFlQ_*!v6Q2Ievn
+zk}vT$f&0EQJ{31!I!{>3YTWBZ8TiATb*UU^+z*+1Wf!=jx+uAu;j?9;{|sNSJ<Ode
+z%eMufPiAm?GylqusxfUhzlKw`_v0mF>uMbp(dAr*h&|>Xe$jnTM&Pv*GWu>?U(IoK
+z12w7sybFGZd}2f`AWuZ?`{?m=DhHYNYW}A_Y}H-{#?)d`)u=#lFt3HmPMPt-e`vUw
+z2oRq{JR12q=YBC6hLE~}Rn5NqV?b6$YdUjPYo)nK34l;80@n0YILoZ5nzNJHrTG@V
+zYy2r1iuu;r01PKXM3@oEUA%;Z<8x`Ve<CKfYj0MLC|*K=b~LIRZ2DNy9Bz~C(h?CQ
+z1ZoiimN>*P^-jjXAz*ox&8O{>dq0F%UFj(|SOhu+*rkDvW`WUIE(Pe2AXx1;LXix3
+z@v_xfL5U17Xm1r}U3;~~E!+YLM@IFc=HzAE!fs?`YDkC$@Fmzf7*Ds9XKMm&v@!2{
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..8dc6fb95f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 281a4f6befca3817adad24ebc9217655ce1ee990 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..03a4bb7a4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From 895f6c9029ee9b853377520610465841e330671e Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..26059047f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From c9ba7f664107b5c8b263fea29fd4823f72536505 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..7d28aff52
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From 0586024620c67c1c85a6a5a0783154d1d2434127 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..700aec6be
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From 532924d3528226217460485d583d9888047f1bb5 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..cb46522ba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From fd17cddb8216905574643146afb91625aa939de6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..4381005a8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From f9722bff21248b9a428953ee4e59334d0c0e78b6 Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..c1f099909
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From 1ee8e0da2cc52303c65f32889e603d930b01bda9 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..766f9f29f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 186660dd8d463122ddf364d6207d7f62645a7404 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..9fb7c2660
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From fb9dab72e6494ffe4c7214c05cfb8931318bdf99 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..402dedcc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 380036aba8f54d3b710e6b635ca0367a5fe94607 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..cfc17049e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From a078b93a1e1e09f5ce66ee1de656a09dc01456f7 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..751b742c1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 185609edb6df9cc8fd1896abb4d9546aa568f67f Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..715d9a41b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 3c55eea13ed7b547ce548518bec7aeb96f4d2662 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..f8760e991
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From e7de712e7507075c4e7bd9f9fe9ff812641887e9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..294255e19
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From 48858b58166b7d49b3f6280781df58b3874ac456 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..740c305b4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From 204d6e0eab7f830aaff7dacfa457a08d4b3e44b7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..6b54b37b9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From 2a0f16e22b3e7ed46d106b25eb1b3c186c84762d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..2a4c71092
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 528b1a27fe38da4186c8548a3249e0c5b04cc8e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..19a8af3f9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From 09d8eddae347bc44e66079e16dc49733d1c220f8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..fb54b8800
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 5a9343847d8343c7ab78a4576a9507405dd33299 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..5c363f735
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From dc4f2714b47a0fb3456e02cf69087eaba3f1ff56 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..c19ad08ae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 27c658d69cd4a0b90745a276a4a09f237993b596 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..88ec3fb87
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From c89cdd7efe73df3c3107abaecea62c20a4cebc97 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..77afa73ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From 5947e49d48a1eb5eabef3e6f3af4001750a322bb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..68bb3320e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From b0ce168c776952e24209d71807727df3729e9021 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..63d48142e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 17b6fcf8c77136d6c9ae29278f0b96bcc80575a9 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..bb941744b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From 71084ed9b3ebf60379251179f9bd5806c76f554c Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..18ae00b5f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 166f6a1236a2627a5c30f8dfffe7ddf7bad956be Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..1ec5b7531
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From 27861b5be77f6d9bbade565a15aaaaab18a1f4a2 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..c4bebf203
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From f6e84e826857c13b583555abb7b53fc993c4b768 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..11ff79ac5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From a45c062b3d792aed5fae393ca763b5399b501e00 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..1f66cb035
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From b158de954ca1cc760ba4833ad4a9e386650723ae Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.39.2
[-- Attachment #2: bitbake-output-qemux86-64.txt --]
[-- Type: text/plain, Size: 9311 bytes --]
NOTE: Reconnecting to bitbake server...
Loading cache...done.
Loaded 4910 entries from dependency cache.
Parsing recipes...done.
Parsing of 3055 .bb files complete (3054 cached, 1 parsed). 4886 targets, 92 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
Build Configuration:
BB_VERSION = "2.6.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "universal"
TARGET_SYS = "x86_64-poky-linux"
MACHINE = "qemux86-64"
DISTRO = "poky"
DISTRO_VERSION = "4.3+snapshot-e575f59b82eae78fb1ac8df7198eaa4a7a963259"
TUNE_FEATURES = "m64 core2"
TARGET_FPU = ""
meta
meta-poky
meta-yocto-bsp
workspace = "master:e575f59b82eae78fb1ac8df7198eaa4a7a963259"
meta-oe
meta-python
meta-perl
meta-networking
meta-multimedia
meta-gnome
meta-xfce
meta-filesystems
meta-initramfs
meta-webserver = "tmp-auh-upgrades:c092a0cbeff5311cffaf21881afbf1af5f469086"
Initialising tasks...NOTE: The /proc/pressure files can't be read. Continuing build without monitoring pressure
done.
Sstate summary: Wanted 380 Local 369 Mirrors 0 Missed 11 Current 291 (97% match, 98% complete)
NOTE: Executing Tasks
NOTE: Running setscene task 303 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_recipe_qa_setscene)
NOTE: Running setscene task 304 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-parse-type_0.6.2.bb:do_create_spdx_setscene)
NOTE: Running setscene task 339 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/python/python3-setuptools_68.2.2.bb:do_create_spdx_setscene)
NOTE: Running setscene task 340 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/python/python3-six_1.16.0.bb:do_create_spdx_setscene)
NOTE: recipe python3-setuptools-68.2.2-r0: task do_create_spdx_setscene: Started
NOTE: recipe python3-behave-6-r0: task do_recipe_qa_setscene: Started
NOTE: recipe python3-six-1.16.0-r0: task do_create_spdx_setscene: Started
NOTE: recipe python3-parse-type-0.6.2-r0: task do_create_spdx_setscene: Started
NOTE: recipe python3-behave-6-r0: task do_recipe_qa_setscene: Succeeded
NOTE: Running task 493 of 1895 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_fetch)
NOTE: recipe python3-setuptools-68.2.2-r0: task do_create_spdx_setscene: Succeeded
NOTE: recipe python3-parse-type-0.6.2-r0: task do_create_spdx_setscene: Succeeded
NOTE: recipe python3-six-1.16.0-r0: task do_create_spdx_setscene: Succeeded
NOTE: Running setscene task 560 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/python/python3_3.11.5.bb:do_create_spdx_setscene)
NOTE: recipe python3-behave-6-r0: task do_fetch: Started
NOTE: recipe python3-3.11.5-r0: task do_create_spdx_setscene: Started
NOTE: recipe python3-behave-6-r0: task do_fetch: Succeeded
NOTE: Running task 503 of 1895 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_unpack)
NOTE: recipe python3-3.11.5-r0: task do_create_spdx_setscene: Succeeded
NOTE: Running setscene task 607 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/gcc/gcc-runtime_13.2.bb:do_create_spdx_setscene)
NOTE: recipe python3-behave-6-r0: task do_unpack: Started
NOTE: recipe gcc-runtime-13.2.0-r0: task do_create_spdx_setscene: Started
NOTE: recipe gcc-runtime-13.2.0-r0: task do_create_spdx_setscene: Succeeded
NOTE: Running setscene task 620 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-core/glibc/glibc_2.38.bb:do_create_spdx_setscene)
NOTE: recipe glibc-2.38+git-r0: task do_create_spdx_setscene: Started
NOTE: recipe glibc-2.38+git-r0: task do_create_spdx_setscene: Succeeded
NOTE: Running setscene task 627 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/gcc/gcc-cross_13.2.bb:do_create_spdx_setscene)
NOTE: Running setscene task 629 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/gcc/gcc-cross_13.2.bb:do_populate_sysroot_setscene)
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_create_spdx_setscene: Started
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_populate_sysroot_setscene: Started
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_create_spdx_setscene: Succeeded
NOTE: recipe python3-behave-6-r0: task do_unpack: Succeeded
NOTE: Running task 1161 of 1895 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch)
NOTE: recipe python3-behave-6-r0: task do_patch: Started
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_populate_sysroot_setscene: Succeeded
NOTE: Running setscene task 663 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/binutils/binutils-cross_2.41.bb:do_populate_sysroot_setscene)
NOTE: Running setscene task 665 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/gcc/gcc-cross_13.2.bb:do_recipe_qa_setscene)
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_recipe_qa_setscene: Started
NOTE: recipe binutils-cross-x86_64-2.41-r0: task do_populate_sysroot_setscene: Started
NOTE: recipe gcc-cross-x86_64-13.2.0-r0: task do_recipe_qa_setscene: Succeeded
NOTE: recipe binutils-cross-x86_64-2.41-r0: task do_populate_sysroot_setscene: Succeeded
NOTE: Running setscene task 668 of 671 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta/recipes-devtools/binutils/binutils-cross_2.41.bb:do_recipe_qa_setscene)
NOTE: recipe binutils-cross-x86_64-2.41-r0: task do_recipe_qa_setscene: Started
NOTE: recipe binutils-cross-x86_64-2.41-r0: task do_recipe_qa_setscene: Succeeded
NOTE: Setscene tasks completed
NOTE: Running task 1689 of 1895 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_prepare_recipe_sysroot)
NOTE: Running task 1862 of 1895 (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_collect_spdx_deps)
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Started
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Started
NOTE: recipe python3-behave-6-r0: task do_prepare_recipe_sysroot: Succeeded
NOTE: recipe python3-behave-6-r0: task do_collect_spdx_deps: Succeeded
NOTE: recipe python3-behave-6-r0: task do_patch: Failed
NOTE: Tasks Summary: Attempted 1880 tasks of which 1875 didn't need to be rerun and 1 failed.
NOTE: Writing buildhistory
NOTE: Writing buildhistory took: 7 seconds
NOTE: The errors for this build are stored in /home/pokybuild/yocto-worker/auh-meta-oe/build/build/tmp/log/error-report/error_report_20231103201021.txt
You can send the errors to a reports server by running:
send-error-report /home/pokybuild/yocto-worker/auh-meta-oe/build/build/tmp/log/error-report/error_report_20231103201021.txt [-s server]
NOTE: The contents of these logs will be posted in public if you use the above command with the default server. Please ensure you remove any identifying or proprietary information when prompted before sending.
Summary: 1 task failed:
/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch
Summary: There was 1 ERROR message, returning a non-zero exit code.
ERROR: python3-behave-6-r0 do_patch: Applying patch '0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch' on target directory '/home/pokybuild/yocto-worker/auh-meta-oe/build/build/tmp/work/core2-64-poky-linux/python3-behave/6/git'
CmdError('quilt --quiltrc /home/pokybuild/yocto-worker/auh-meta-oe/build/build/tmp/work/core2-64-poky-linux/python3-behave/6/recipe-sysroot-native/etc/quiltrc push', 0, "stdout: Applying patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
can't find file to patch at input line 15
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|From 0ae5e867f73ba50744531024e3bad79979f1dbaa Mon Sep 17 00:00:00 2001
|From: jenisys <jenisys@users.noreply.github.com>
|Date: Mon, 11 Mar 2019 22:37:04 +0100
|Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
| description to created Scenario.
|
|---
| behave/model.py | 1 +
| 1 file changed, 1 insertion(+)
|
|diff --git a/behave/model.py b/behave/model.py
|index 4ad4b9d..9dd68fd 100644
|--- a/behave/model.py
|+++ b/behave/model.py
--------------------------
No file to patch. Skipping patch.
1 out of 1 hunk ignored
Patch 0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch does not apply (enforce with -f)
stderr: ")
ERROR: Logfile of failure stored in: /home/pokybuild/yocto-worker/auh-meta-oe/build/build/tmp/work/core2-64-poky-linux/python3-behave/6/temp/log.do_patch.2796146
ERROR: Task (/home/pokybuild/yocto-worker/auh-meta-oe/build/meta-openembedded/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb:do_patch) failed with exit code '1'
[-- Attachment #3: 0001-python3-behave-upgrade-1.2.6-6.patch --]
[-- Type: application/octet-stream, Size: 1245978 bytes --]
From d9ceb27176c0404fd6c577dbebd3ad671b58af1c Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@yoctoproject.org>
Date: Fri, 3 Nov 2023 20:10:50 +0000
Subject: [PATCH] python3-behave: upgrade 1.2.6 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
| 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
| 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..e94b2f25c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@
+From 0ae5e867f73ba50744531024e3bad79979f1dbaa Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..8a113a20f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@
+From 34a1d26a1683d4d952da7f32c02316d67cb13925 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..e0f34b532
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@
+From 92baa7df57b5db861a03c80034a52e6e7681c8d2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..6ed826b97
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@
+From a0678cdece593697082973abbb59648f27530c99 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..6c5915c4d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@
+From bd74dcc8af6b48d15a1f2e8158aa53565a4bb45c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..8fa8b59fa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@
+From 1cc411a82767fd63b4eff5bc9cff6d7867d45d17 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..ffd433857
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@
+From 466337566f163bc7339fe20d12d8fb8561eabbdf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..c0e9a67bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@
+From 6b698d73616c3eefbf67c0bb184755af5334f37f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..a3d12df88
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@
+From c27f43ef8270c7e08a20fbb3066c8780f26ecccd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..71bcb8d37
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@
+From 9b5204ebaa619f2776f2c6350c619927327e5aca Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..94e7d6352
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@
+From f7313c14ee5b1038447d104a885bd0f99875b3e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..b8f2527a0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@
+From 1c8b368ed9016f9db7f17851144250712717f1c5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..dcf234771
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@
+From 60438ebea003efae558ea344fbeb633cf21a8267 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..5eedb5875
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@
+From 7a7984717e1d5278bac29283e0870f0a638b22fe Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..584931bd9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@
+From 603bae920105bb97b4dfe2979d08cfea61e72dd8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..dec47bbbf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@
+From 4a27977f21a0179e8d0c01581e93ee689f0b992e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..d1dd355ab
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@
+From 2bdb1cd02f10b307e11df7f33a410401ae8a49b1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..83c01dc49
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@
+From 4c62d7eaf7efd930b6d4d681c5ebe0cd901e529e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..d5535afe9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@
+From 89da35f7b165d74cce802aff5ba9b4833bb2171d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..b72886009
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@
+From 756f9f7e6254e576315880cee3feb32e9dc7c452 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..5cd5ca8de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@
+From 34e0fc3657577c00931663501087788188b5bfdf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..462fbb6d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@
+From ebdd9ce8253ec22f1e31dbb5cc42702c949af301 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..3c6eda380
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@
+From 67eb237284f89d694bb09ebf7a3166c6c08cb025 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..f39386642
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@
+From 958e8ddce6dd80b211c336e7b809b10e92ef1fa5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..7082dbc25
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@
+From 5a039c49d0e158013bf308e824212decd3085fbf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..d8f68c3bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@
+From 5619b3375f9a4bbf760dafd6e77945658d5e06e6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
--git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..b4f54a2d2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@
+From 2fdad16ca8bbca127b6b181da2ccf4eebb58a8a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..d40c78afa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@
+From 2d151afe0191a8c227cb873e577575eb9d78057f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..f85f73e83
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@
+From 18784841314072f089f47ef208851ae475271acd Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..3839c8d55
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@
+From 1b9fa431aa92cfee62c1efcfd74f72774707e6b2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..01b30c7ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@
+From 17fd92cd7fd764757651097e9bd947ce35e39196 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..a9e619c58
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@
+From f6f22d97662e74fd728648f6d42a7f07bfc60dee Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..4f520396e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@
+From 18bf6e97784b4d1b4c91833826af8963e0daf612 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..f99a1c226
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@
+From 029ed12454b56682849ccd0cec00c9cb4611e23e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..714e3a530
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@
+From 878af8e5a05b138ecbb63bcfcae522669cabb034 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..3b0f4823a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@
+From e706a7720cc8e75b4fd8e43e43139901a701b16b Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..8021f2088
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@
+From 6b9f9eb25283c2bbdc742aa2f3fcd5bcb33ea618 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..d64c466f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@
+From 97f18f67e7ede362b5817700521df790479d8061 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..9041195d5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@
+From 9b451c67a09acafa4c82af278535368cb1ee9b4a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..44611cc84
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@
+From 6fcb1210f8d2790c41475e2eac6de70095da7865 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..94aaee98d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@
+From 89181e0c0edef7b723f4d314c5bb55b32368df49 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++📚: 🙈🙉🙊
++
++ 📕: 💃
++ 😐🎸
+diff --git a/features/steps/i18n_emoji_steps.py b/features/steps/i18n_emoji_steps.py
+new file mode 100644
+index 0000000..381d2bb
+--- /dev/null
++++ b/features/steps/i18n_emoji_steps.py
+@@ -0,0 +1,10 @@
++# -*- coding: UTF-8 -*-
++# NEEDED-BY: features/i18n_emoji.feature
++
++from behave import given
++
++@given(u'🎸')
++def step_impl(context):
++ """Step implementation example with emoji(s)."""
++ pass
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
new file mode 100644
index 000000000..60cc8cc4c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
@@ -0,0 +1,250 @@
+From a904d8961c44cb101fe29b367f979453e8809c8a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:06:11 +0200
+Subject: [PATCH] FIX: Invalid escape char (in regex) w/ python3.
+
+---
+ behave/formatter/ansi_escapes.py | 53 ++++++++----
+ tests/unit/test_ansi_escapes.py | 144 ++++++++++++++++++-------------
+ 2 files changed, 122 insertions(+), 75 deletions(-)
+
+diff --git a/behave/formatter/ansi_escapes.py b/behave/formatter/ansi_escapes.py
+index 6c93e6c..3ec84db 100644
+--- a/behave/formatter/ansi_escapes.py
++++ b/behave/formatter/ansi_escapes.py
+@@ -7,6 +7,9 @@ from __future__ import absolute_import
+ import os
+ import re
+
++# ---------------------------------------------------------------------------
++# MODULE DATA
++# ---------------------------------------------------------------------------
+ colors = {
+ "black": u"\x1b[30m",
+ "red": u"\x1b[31m",
+@@ -38,27 +41,48 @@ escapes = {
+ "up": u"\x1b[1A",
+ }
+
+-if "GHERKIN_COLORS" in os.environ:
+- new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
+- aliases.update(dict(new_aliases))
+
+-for alias in aliases:
+- escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
+- arg_alias = alias + "_arg"
+- arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
+- escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++# -- NEEDED-FOR: strip_escapes(), ...
++_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\\[\\d+[mA]", re.UNICODE)
+
+
+-# pylint: disable=anomalous-backslash-in-string
++
++# ---------------------------------------------------------------------------
++# MODULE SETUP
++# ---------------------------------------------------------------------------
++def _setup_module():
++ """Setup the remaining ANSI color aliases and ANSI escape sequences.
++
++ .. note:: May modify/extend the module attributes:
++
++ * :attr:`aliases`
++ * :attr:`escapes`
++ """
++ # MAYBE: global aliases, escapes
++ if "GHERKIN_COLORS" in os.environ:
++ new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")]
++ aliases.update(dict(new_aliases))
++
++ for alias in aliases:
++ escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")])
++ arg_alias = alias + "_arg"
++ arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold")
++ escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")])
++
++
++# -- ONCE: During module-import.
++_setup_module()
++
++
++# ---------------------------------------------------------------------------
++# FUNCTIONS:
++# ---------------------------------------------------------------------------
+ def up(n):
+ return u"\x1b[%dA" % n
+
+
+-_ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE)
+-# pylint: enable=anomalous-backslash-in-string
+ def strip_escapes(text):
+- """
+- Removes ANSI escape sequences from text (if any are contained).
++ """Removes ANSI escape sequences from text (if any are contained).
+
+ :param text: Text that may or may not contain ANSI escape sequences.
+ :return: Text without ANSI escape sequences.
+@@ -67,8 +91,7 @@ def strip_escapes(text):
+
+
+ def use_ansi_escape_colorbold_composites(): # pragma: no cover
+- """
+- Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
++ """Patch for "sphinxcontrib-ansi" to process the following ANSI escapes
+ correctly (set-color set-bold sequences):
+
+ ESC[{color}mESC[1m => ESC[{color};1m
+diff --git a/tests/unit/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+index 969f3a9..4fb78c2 100644
+--- a/tests/unit/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -9,65 +9,89 @@
+ from __future__ import absolute_import
+ import pytest
+ from behave.formatter import ansi_escapes
+-import unittest
+ from six.moves import range
+
+-class StripEscapesTest(unittest.TestCase):
+- ALL_COLORS = list(ansi_escapes.colors.keys())
+- CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ]
+- TEXTS = [
+- u"lorem ipsum",
+- u"Alice\nBob\nCharly\nDennis",
+- ]
+-
+- @classmethod
+- def colorize(cls, text, color):
+- color_escape = ""
+- if color:
+- color_escape = ansi_escapes.colors[color]
+- return color_escape + text + ansi_escapes.escapes["reset"]
+-
+- @classmethod
+- def colorize_text(cls, text, colors=None):
+- if not colors:
+- colors = []
+- colors_size = len(colors)
+- color_index = 0
+- colored_chars = []
+- for char in text:
+- color = colors[color_index]
+- colored_chars.append(cls.colorize(char, color))
+- color_index += 1
+- if color_index >= colors_size:
+- color_index = 0
+- return "".join(colored_chars)
+-
+- def test_should_return_same_text_without_escapes(self):
+- for text in self.TEXTS:
+- assert text == ansi_escapes.strip_escapes(text)
+-
+- def test_should_return_empty_string_for_any_ansi_escape(self):
+- # XXX-JE-CHECK-PY23: If list() is really needed.
+- for text in list(ansi_escapes.colors.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+- for text in list(ansi_escapes.escapes.values()):
+- assert "" == ansi_escapes.strip_escapes(text)
+-
+-
+- def test_should_strip_color_escapes_from_text(self):
+- for text in self.TEXTS:
+- colored_text = self.colorize_text(text, self.ALL_COLORS)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- for color in self.ALL_COLORS:
+- colored_text = self.colorize(text, color)
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
+-
+- def test_should_strip_cursor_up_escapes_from_text(self):
+- for text in self.TEXTS:
+- for cursor_up in self.CURSOR_UPS:
+- colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- assert text == ansi_escapes.strip_escapes(colored_text)
+- self.assertNotEqual(text, colored_text)
++
++# --------------------------------------------------------------------------
++# TEST SUPPORT and TEST DATA
++# --------------------------------------------------------------------------
++TEXTS = [
++ u"lorem ipsum",
++ u"Alice and Bob",
++ u"Alice\nBob",
++]
++ALL_COLORS = list(ansi_escapes.colors.keys())
++CURSOR_UPS = [ansi_escapes.up(count) for count in range(10)]
++
++
++def colorize(text, color):
++ color_escape = ""
++ if color:
++ color_escape = ansi_escapes.colors[color]
++ return color_escape + text + ansi_escapes.escapes["reset"]
++
++
++def colorize_text(text, colors=None):
++ if not colors:
++ colors = []
++ colors_size = len(colors)
++ color_index = 0
++ colored_chars = []
++ for char in text:
++ color = colors[color_index]
++ colored_chars.append(colorize(char, color))
++ color_index += 1
++ if color_index >= colors_size:
++ color_index = 0
++ return "".join(colored_chars)
++
++
++# --------------------------------------------------------------------------
++# TEST SUITE
++# --------------------------------------------------------------------------
++def test_module_setup():
++ """Ensure that the module setup (aliases, escapes) occured."""
++ # colors_count = len(ansi_escapes.colors)
++ aliases_count = len(ansi_escapes.aliases)
++ escapes_count = len(ansi_escapes.escapes)
++ assert escapes_count >= (2 + aliases_count + aliases_count)
++
++
++class TestStripEscapes(object):
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_return_same_text_without_escapes(self, text):
++ assert text == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.colors.values())
++ def test_should_return_empty_string_for_any_ansi_escape_color(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", ansi_escapes.escapes.values())
++ def test_should_return_empty_string_for_any_ansi_escape(self, text):
++ assert "" == ansi_escapes.strip_escapes(text)
++
++ @pytest.mark.parametrize("text", TEXTS)
++ def test_should_strip_color_escapes_from_all_colored_text(self, text):
++ colored_text = colorize_text(text, ALL_COLORS)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("color", ALL_COLORS)
++ def test_should_strip_color_escapes_from_text(self, text, color):
++ colored_text = colorize(text, color)
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
++
++ colored_text2 = colorize(text, color) + text
++ text2 = text + text
++ assert text2 == ansi_escapes.strip_escapes(colored_text2)
++ assert text2 != colored_text2
++
++ @pytest.mark.parametrize("text", TEXTS)
++ @pytest.mark.parametrize("cursor_up", CURSOR_UPS)
++ def test_should_strip_cursor_up_escapes_from_text(self, text, cursor_up):
++ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
++ assert text == ansi_escapes.strip_escapes(colored_text)
++ assert text != colored_text
diff --git a/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
new file mode 100644
index 000000000..300408616
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
@@ -0,0 +1,335 @@
+From e330aacc06169664183eb05a5fffcfd902d602ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 19:15:44 +0200
+Subject: [PATCH] Example related to question in #756
+
+---
+ .../fixture.override_background/README.rst | 116 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 ++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 274 insertions(+)
+ create mode 100644 examples/fixture.override_background/README.rst
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ create mode 100644 examples/fixture.override_background/features/environment.py
+ create mode 100644 examples/fixture.override_background/features/example.feature
+ create mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+new file mode 100644
+index 0000000..9c150cc
+--- /dev/null
++++ b/examples/fixture.override_background/README.rst
+@@ -0,0 +1,116 @@
++EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@ixture.behave.override_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.override_background")
++ def behave_override_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++ # -----------------------------------------------------------------------------
++ # BEHAVE UTILITY:
++ # -----------------------------------------------------------------------------
++ def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+new file mode 100644
+index 0000000..6c572cf
+--- /dev/null
++++ b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+@@ -0,0 +1,80 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.override_background")
++def behave_override_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ behave_disable_background_inheritance_for_scenario(current_scenario)
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE UTILITY:
++# -----------------------------------------------------------------------------
++def behave_disable_background_inheritance_for_scenario(scenario):
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
++ scenario.background = None
++ # scenario._background_steps = []
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+new file mode 100644
+index 0000000..7a4b735
+--- /dev/null
++++ b/examples/fixture.override_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.override_background import behave_override_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.overide_background": behave_override_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+new file mode 100644
+index 0000000..5ddd874
+--- /dev/null
++++ b/examples/fixture.override_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Override the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.overide_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
new file mode 100644
index 000000000..b1059c4ff
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
@@ -0,0 +1,489 @@
+From a0e549bc9595c23f7eff7202f618695bf7a548a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:43:19 +0200
+Subject: [PATCH] FIX: python3.8 regressions on CI server
+
+Issues w/ python3.8:
+* Traceback: Line numbers of step functions differ (+1)
+* JUnit XML: Attribute ordering of XML element differs (in output)
+---
+ behave.ini | 3 +-
+ features/environment.py | 14 ++++++
+ features/step.duplicated_step.feature | 20 ++++----
+ issue.features/environment.py | 38 ++++++++++++---
+ issue.features/issue0330.feature | 64 ++++++++++++++++++++++++
+ issue.features/issue0446.feature | 70 +++++++++++++++++++++++++++
+ issue.features/issue0457.feature | 49 +++++++++++++++++++
+ tox.ini | 2 +-
+ 8 files changed, 242 insertions(+), 18 deletions(-)
+
+diff --git a/behave.ini b/behave.ini
+index 45c0f0d..952240d 100644
+--- a/behave.ini
++++ b/behave.ini
+@@ -15,8 +15,9 @@ show_skipped = false
+ format = rerun
+ progress3
+ outfiles = rerun.txt
+- reports/report_progress3.txt
++ build/behave.reports/report_progress3.txt
+ junit = true
++junit_directory = build/behave.reports
+ logging_level = INFO
+ # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
+ # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
+diff --git a/features/environment.py b/features/environment.py
+index 4744e89..3769ee4 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -1,5 +1,7 @@
+ # -*- coding: UTF-8 -*-
++# FILE: features/environemnt.py
+
++from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ import platform
+@@ -20,6 +22,15 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -30,11 +41,14 @@ def before_all(context):
+ setup_python_path()
+ setup_context_with_global_params_test(context)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 59888b0..396cca2 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Alice') has already been defined in
+ existing step @given('I call Alice') at features/steps/alice_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/alice_steps.py", line 7, in <module>
+- @given(u'I call Alice')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/alice_steps.py", line 7, in <module>
++ # """
+
+
+ Scenario: Duplicated Step Definition in another File
+@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
+ AmbiguousStep: @given('I call Bob') has already been defined in
+ existing step @given('I call Bob') at features/steps/bob1_steps.py:3
+ """
+- And the command output should contain:
+- """
+- File "features/steps/bob2_steps.py", line 3, in <module>
+- @given('I call Bob')
+- """
++ # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
++ # And the command output should contain:
++ # """
++ # File "features/steps/bob2_steps.py", line 3, in <module>
++ # """
+
+ @xfail
+ Scenario: Duplicated Same Step Definition via import from another File
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 2dfec75..7e48ee0 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-# FILE: features/environment.py
++# FILE: issue.features/environemnt.py
+ # pylint: disable=unused-argument
+ """
+ Functionality:
+@@ -7,17 +7,20 @@ Functionality:
+ * active tags
+ """
+
+-from __future__ import print_function
++
++from __future__ import absolute_import, print_function
+ import sys
+ import platform
+ import os.path
+ import six
+ from behave.tag_matcher import ActiveTagMatcher
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+-# PREPARED:
+-# from behave.tag_matcher import setup_active_tag_values
++# PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+
++# ---------------------------------------------------------------------------
++# TEST SUPPORT: For Active Tags
++# ---------------------------------------------------------------------------
+ def require_tool(tool_name):
+ """Check if a tool (an executable program) is provided on this platform.
+
+@@ -45,12 +48,14 @@ def require_tool(tool_name):
+ # print("TOOL-NOT-FOUND: %s" % tool_name)
+ return False
+
++
+ def as_bool_string(value):
+ if bool(value):
+ return "yes"
+ else:
+ return "no"
+
++
+ def discover_ci_server():
+ # pylint: disable=invalid-name
+ ci_server = "none"
+@@ -67,12 +72,17 @@ def discover_ci_server():
+ return ci_server
+
+
++# ---------------------------------------------------------------------------
++# BEHAVE SUPPORT: Active Tags
++# ---------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+ "platform": sys.platform,
+ "python2": str(six.PY2).lower(),
+ "python3": str(six.PY3).lower(),
++ "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+@@ -82,17 +92,33 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
++def print_active_tags_summary():
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAG SUMMARY:")
++ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
++ # print("use.with_platform=%s" % active_tag_data.get("platform"))
++ # print("use.with_os=%s" % active_tag_data.get("os"))
++ print()
++
++
++# ---------------------------------------------------------------------------
++# BEHAVE HOOKS:
++# ---------------------------------------------------------------------------
+ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ # USE: behave -D browser=safari ...
+- # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
+- # context.config.userdata)
++ # NOT-NEEDED:
++ # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+ setup_command_shell_processors4behave()
++ print_active_tags_summary()
++
+
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index dc1ebe7..81cb6e2 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "bob.feature is skipped"
+
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped feature is created with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 1 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-bob.xml" exists
++ And the file "test_results/TESTS-bob.xml" should contain:
++ """
++ <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
++
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ When I run "behave --junit -t @tag1 --no-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
++ And the file "test_results/TESTS-charly.xml" should not contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+
++ @not.with_python.version=3.8
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
++
++ @use.with_python.version=3.8
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ When I run "behave --junit -t @tag1 --show-skipped"
++ Then it should pass with:
++ """
++ 2 features passed, 0 failed, 1 skipped
++ 2 scenarios passed, 0 failed, 2 skipped
++ """
++ And a file named "test_results/TESTS-alice.xml" exists
++ And a file named "test_results/TESTS-charly.xml" exists
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
++ """
++ # HINT: Python < 3.8
++ # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
++ And the file "test_results/TESTS-charly.xml" should contain:
++ """
++ <testcase classname="charly.Charly" name="Charly2" status="skipped"
++ """
++ And note that "Charly2 is the skipped scenarion in charly.feature"
++
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index a2ed892..901bdec 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ behave.reporter.junit.show_hostname = False
+ """
+
++ @not.with_python.version=3.8
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ And note that "the traceback is contained in the XML element <error/>"
+
+
++ @use.with_python.version=3.8
++ Scenario: Hook error in before_scenario()
++ When I run "behave -f plain --junit features/before_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ HOOK-ERROR in before_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-before_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 6, in before_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @not.with_python.version=3.8
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ raise RuntimeError("OOPS")
+ """
+ And note that "the traceback is contained in the XML element <error/>"
++
++
++ @use.with_python.version=3.8
++ Scenario: Hook error in after_scenario()
++ When I run "behave -f plain --junit features/after_scenario_failure.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ Scenario: B1
++ Given another step passes ... passed
++ HOOK-ERROR in after_scenario: RuntimeError: OOPS
++ """
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
++ """
++ # -- HINT FOR: Python < 3.8
++ # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
++ And the file "reports/TESTS-after_scenario_failure.xml" should contain:
++ """
++ File "features/environment.py", line 10, in after_scenario
++ cause_hook_failure()
++ File "features/environment.py", line 2, in cause_hook_failure
++ raise RuntimeError("OOPS")
++ """
++ And note that "the traceback is contained in the XML element <error/>"
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index f80640e..46f96e9 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+
++ @not.with_python.version=3.8
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <failure message="FAILED: My name is "Alice""
+ """
+
++ @use.with_python.version=3.8
++ Scenario: Use failing assertation in a JUnit XML report
++ Given a file named "features/fails1.feature" with:
++ """
++ Feature:
++ Scenario: Alice
++ Given a step fails with message:
++ '''
++ My name is "Alice"
++ '''
++ """
++ When I run "behave --junit features/fails1.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails1.xml" should contain:
++ """
++ <failure type="AssertionError" message="FAILED: My name is "Alice"">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <failure message="FAILED: My name is "Alice""
++
++
++ @not.with_python.version=3.8
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+ <error message="My name is "Bob" and <here> I am"
+ """
++
++ @use.with_python.version=3.8
++ Scenario: Use exception in a JUnit XML report
++ Given a file named "features/fails2.feature" with:
++ """
++ Feature:
++ Scenario: Bob
++ Given a step fails with error and message:
++ '''
++ My name is "Bob" and <here> I am
++ '''
++ """
++ When I run "behave --junit features/fails2.feature"
++ Then it should fail with:
++ """
++ 0 scenarios passed, 1 failed, 0 skipped
++ """
++ And the file "reports/TESTS-fails2.xml" should contain:
++ """
++ <error type="RuntimeError" message="My name is "Bob" and <here> I am">
++ """
++ # -- HINT FOR: Python < 3.8
++ # <error message="My name is "Bob" and <here> I am"
+diff --git a/tox.ini b/tox.ini
+index d2fbce2..b825921 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py36, py35, py34, py33, pypy, docs
++envlist = py27, py37, py38, py36, py35, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
new file mode 100644
index 000000000..568ea5407
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
@@ -0,0 +1,46 @@
+From eb55a7ba0f503f22dc37312db544e2307c23f123 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 12:59:57 +0200
+Subject: [PATCH] UPDATE: Mark issue #755 as fixed.
+
+---
+ CHANGES.rst | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d165275..312cbba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -27,28 +27,25 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+-
+-PARTIALLY FIXED:
+-
+-* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+
+ FIXED:
+
+-* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
+ * issue #619: Context __getattr__ should raise AttributeError instead of KeyError (submitted by: anxodio)
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+
+ MINOR:
+
++* pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+-* pull #660: Fix minor typos (provided by: rrueth)
+
+ DOCUMENTATION:
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
new file mode 100644
index 000000000..8f466fd60
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
@@ -0,0 +1,57 @@
+From 5bdac70928fffefb2c17eb499af00e6c5216a38e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 7 Jul 2019 23:35:45 +0200
+Subject: [PATCH] UPDATE: Cucumber gherkin-languages.json
+
+---
+ behave/i18n.py | 5 ++++-
+ etc/gherkin/gherkin-languages.json | 6 +++++-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 9eb9aab..721c4c3 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -382,6 +382,9 @@ languages = \
+ 'feature': ['Fonctionnalité'],
+ 'given': ['* ',
+ 'Soit ',
++ 'Sachant que ',
++ "Sachant qu'",
++ 'Sachant ',
+ 'Etant donné que ',
+ "Etant donné qu'",
+ 'Etant donné ',
+@@ -399,7 +402,7 @@ languages = \
+ 'rule': ['Règle'],
+ 'scenario': ['Exemple', 'Scénario'],
+ 'scenario_outline': ['Plan du scénario', 'Plan du Scénario'],
+- 'then': ['* ', 'Alors '],
++ 'then': ['* ', 'Alors ', 'Donc '],
+ 'when': ['* ', 'Quand ', 'Lorsque ', "Lorsqu'"]},
+ 'ga': {'and': ['* ', 'Agus'],
+ 'background': ['Cúlra'],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index b08e0f5..913cfac 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1256,6 +1256,9 @@
+ "given": [
+ "* ",
+ "Soit ",
++ "Sachant que ",
++ "Sachant qu'",
++ "Sachant ",
+ "Etant donné que ",
+ "Etant donné qu'",
+ "Etant donné ",
+@@ -1284,7 +1287,8 @@
+ ],
+ "then": [
+ "* ",
+- "Alors "
++ "Alors ",
++ "Donc "
+ ],
+ "when": [
+ "* ",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
new file mode 100644
index 000000000..3a2ca3100
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
@@ -0,0 +1,202 @@
+From 85baf33a50a824c7006a7981cb80871728a47265 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 00:38:26 +0200
+Subject: [PATCH] gherkin: Adding Rule keyword translation in portuguese and
+ spanish to gherkin-languages.json
+
+* Integrate changes based on merged cucumber pull-request #621
+* Update "gherkin-languages.json"
+* Update "behave/i18n.py" (generated from: gherkin-languages.json)
+
+RELATED-TO: pull #751 (same; using the official way)
+---
+ CHANGES.rst | 1 +
+ behave/fixture.py | 1 -
+ behave/i18n.py | 4 +--
+ etc/gherkin/gherkin-languages.json | 4 +--
+ invoke.yaml | 4 +++
+ tasks/__init__.py | 3 ++
+ tasks/develop.py | 58 ++++++++++++++++++++++++++++++
+ tasks/py.requirements.txt | 3 ++
+ 8 files changed, 73 insertions(+), 5 deletions(-)
+ create mode 100644 tasks/develop.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 312cbba..15a4ef9 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+ * issue #654: tox.ini: pypi.python.org -> pypi.org (submitted by: pradyunsg)
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 21093b0..3a9f1bc 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -348,7 +348,6 @@ def use_composite_fixture_with(context, fixture_funcs_with_params):
+ return composite_fixture
+
+
+-
+ # -------------------------------------------------------------------------------
+ # DECORATORS:
+ # -------------------------------------------------------------------------------
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 721c4c3..2781afe 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -331,7 +331,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Spanish',
+ 'native': 'español',
+- 'rule': ['Rule'],
++ 'rule': ['Regla'],
+ 'scenario': ['Ejemplo', 'Escenario'],
+ 'scenario_outline': ['Esquema del escenario'],
+ 'then': ['* ', 'Entonces '],
+@@ -762,7 +762,7 @@ languages = \
+ 'given': ['* ', 'Dado ', 'Dada ', 'Dados ', 'Dadas '],
+ 'name': 'Portuguese',
+ 'native': 'português',
+- 'rule': ['Rule'],
++ 'rule': ['Regra'],
+ 'scenario': ['Exemplo', 'Cenário', 'Cenario'],
+ 'scenario_outline': ['Esquema do Cenário',
+ 'Esquema do Cenario',
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 913cfac..29cbca1 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -1084,7 +1084,7 @@
+ "name": "Spanish",
+ "native": "español",
+ "rule": [
+- "Rule"
++ "Regla"
+ ],
+ "scenario": [
+ "Ejemplo",
+@@ -2553,7 +2553,7 @@
+ "name": "Portuguese",
+ "native": "português",
+ "rule": [
+- "Rule"
++ "Regra"
+ ],
+ "scenario": [
+ "Exemplo",
+diff --git a/invoke.yaml b/invoke.yaml
+index 3e93cfc..d6f141c 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -38,6 +38,10 @@ cleanup:
+ - "__WORKDIR__"
+ - reports
+
++ extra_files:
++ - "etc/gherkin/gherkin*.json.SAVED"
++ - "etc/gherkin/i18n.py"
++
+ cleanup_all:
+ extra_directories:
+ - .hypothesis
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index 969a94a..a572465 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -39,6 +39,8 @@ from . import _tasklet_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
++from . import develop
++
+
+ # -----------------------------------------------------------------------------
+ # TASKS:
+@@ -56,6 +58,7 @@ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
++namespace.add_collection(Collection.from_module(develop))
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+diff --git a/tasks/develop.py b/tasks/develop.py
+new file mode 100644
+index 0000000..b08df0e
+--- /dev/null
++++ b/tasks/develop.py
+@@ -0,0 +1,58 @@
++# -*- coding: UTF-8 -*-
++"""
++Development tasks
++"""
++
++from __future__ import absolute_import, print_function
++from invoke import Collection, task
++from invoke.util import cd
++from path import Path
++import requests
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json"
++
++
++# -----------------------------------------------------------------------------
++# TASKS:
++# -----------------------------------------------------------------------------
++@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
++def update_gherkin_languages(ctx):
++ """Update "gherkin-languages.json" file from cucumber-repo."""
++ with cd("etc/gherkin"):
++ # -- BACKUP-FILE:
++ gherkin_languages_file = Path("gherkin-languages.json")
++ gherkin_languages_file.copy("gherkin-languages.json.SAVED")
++
++ print('Downloading "gherkin-languages.json" from github:cucumber ...')
++ download_request = requests.get(GHERKIN_LANGUAGES_URL)
++ gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
++ assert download_request.ok
++ print('Download finished: OK (size={0})'.format(len(download_request.content)))
++ with open(gherkin_languages_newfile, "wb") as f:
++ f.write(download_request.content)
++ gherkin_languages_newfile.rename("gherkin-languages.json")
++
++ print('Generating "i18n.py" ...')
++ ctx.run("./convert_gherkin-languages.py")
++
++
++# -----------------------------------------------------------------------------
++# TASK HELPERS:
++# -----------------------------------------------------------------------------
++def print_packages(packages):
++ print("PACKAGES[%d]:" % len(packages))
++ for package in packages:
++ package_size = package.stat().st_size
++ package_time = package.stat().st_mtime
++ print(" - %s (size=%s)" % (package, package_size))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++namespace = Collection()
++namespace.add_task(update_gherkin_languages)
++namespace.configure({})
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index e772d5e..a77d3bc 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -16,3 +16,6 @@ six >= 1.12.0
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
++
++# -- SECTION: develop
++requests
diff --git a/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
new file mode 100644
index 000000000..c5fb4110d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
@@ -0,0 +1,141 @@
+From 539683fff83ddf41d0b342b4c6c906c64f424fb9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 8 Jul 2019 01:07:11 +0200
+Subject: [PATCH] Tweaks to update/generate from gherkin-languages.json
+
+---
+ etc/gherkin/convert_gherkin-languages.py | 16 +++++++----
+ tasks/develop.py | 34 +++++++++++-------------
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+diff --git a/etc/gherkin/convert_gherkin-languages.py b/etc/gherkin/convert_gherkin-languages.py
+index 1803ca6..9ef9b0c 100755
+--- a/etc/gherkin/convert_gherkin-languages.py
++++ b/etc/gherkin/convert_gherkin-languages.py
+@@ -68,7 +68,7 @@ def yaml_normalize(data):
+ return data
+
+
+-def data_normalize(data):
++def data_normalize(data, verbose=False):
+ """Normalize "gherkin-languages.json" data into internal format,
+ needed by behave."
+
+@@ -76,7 +76,8 @@ def data_normalize(data):
+ :return: Normalized data (as dictionary).
+ """
+ for language in data:
+- print("Language: %s ..." % language)
++ if verbose:
++ print("Language: %s ..." % language)
+ # -- STEP: Normalize attribute "scenarioOutline" => "scenario_outline"
+ lang_keywords = data[language]
+ lang_keywords[u"scenario_outline"] = lang_keywords[u"scenarioOutline"]
+@@ -107,7 +108,7 @@ def data_normalize(data):
+
+
+ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+- encoding=None):
++ encoding=None, verbose=False):
+ """Workhorse.
+ Performs the conversion from "gherkin-languages.json" to "i18n.py".
+ Writes output to file or console (stdout).
+@@ -115,6 +116,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ :param gherkin_languages_path: File path for JSON file.
+ :param output_file: Output filename (or STDOUT for: None, "stdout", "-")
+ :param encoding: Optional output encoding to use (default: UTF-8).
++ :param verbose: Enable verbose mode (as bool; optional).
+ """
+ if encoding is None:
+ encoding = "UTF-8"
+@@ -122,7 +124,7 @@ def gherkin_languages_to_python_module(gherkin_languages_path, output_file=None,
+ # -- STEP 1: Load JSON data.
+ json_encoding = "UTF-8"
+ languages = json.load(open(gherkin_languages_path, encoding=json_encoding))
+- languages = data_normalize(languages)
++ languages = data_normalize(languages, verbose=verbose)
+ # languages = yaml_normalize(languages)
+
+ # -- STEP 2: Generate python module with i18n data.
+@@ -178,6 +180,9 @@ def main(args=None):
+ parser.add_argument("-e", "--encoding", dest="encoding",
+ default="UTF-8",
+ help="Output encoding.")
++ parser.add_argument("--verbose", dest="verbose", default=False,
++ action="store_true",
++ help="Enable verbose mode.")
+ parser.add_argument("output_file", default="i18n.py", nargs="?",
+ help="Filename of Python I18N module (as output).")
+ parser.add_argument("--version", action="version", version=__version__)
+@@ -191,7 +196,8 @@ def main(args=None):
+ try:
+ print("Writing %s .." % options.output_file)
+ gherkin_languages_to_python_module(options.json_file, options.output_file,
+- encoding=options.encoding)
++ encoding=options.encoding,
++ verbose=options.verbose)
+ except Exception as e:
+ message = "%s: %s" % (e.__class__.__name__, e)
+ sys.exit(message)
+diff --git a/tasks/develop.py b/tasks/develop.py
+index b08df0e..9a21363 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -18,9 +18,15 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task(name="update_gherkin") # TOO-LONGS: aliases=["update_gherkin_languages"])
+-def update_gherkin_languages(ctx):
+- """Update "gherkin-languages.json" file from cucumber-repo."""
++@task
++def update_gherkin(ctx, dry_run=False):
++ """Update "gherkin-languages.json" file from cucumber-repo.
++
++ * Download "gherkin-languages.json" from cucumber repo
++ * Update "gherkin-languages.json"
++ * Generate "i18n.py" file from "gherkin-languages.json"
++ * Update "behave/i18n.py" file (optional; not in dry-run mode)
++ """
+ with cd("etc/gherkin"):
+ # -- BACKUP-FILE:
+ gherkin_languages_file = Path("gherkin-languages.json")
+@@ -28,31 +34,23 @@ def update_gherkin_languages(ctx):
+
+ print('Downloading "gherkin-languages.json" from github:cucumber ...')
+ download_request = requests.get(GHERKIN_LANGUAGES_URL)
+- gherkin_languages_newfile = Path("gherkin-languages.json.NEW")
+ assert download_request.ok
+ print('Download finished: OK (size={0})'.format(len(download_request.content)))
+- with open(gherkin_languages_newfile, "wb") as f:
++ with open(gherkin_languages_file, "wb") as f:
+ f.write(download_request.content)
+- gherkin_languages_newfile.rename("gherkin-languages.json")
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK HELPERS:
+-# -----------------------------------------------------------------------------
+-def print_packages(packages):
+- print("PACKAGES[%d]:" % len(packages))
+- for package in packages:
+- package_size = package.stat().st_size
+- package_time = package.stat().st_mtime
+- print(" - %s (size=%s)" % (package, package_size))
++ ctx.run("diff i18n.py ../../behave/i18n.py")
++ if not dry_run:
++ print("Updating behave/i18n.py ...")
++ Path("i18n.py").move("../../behave/i18n.py")
+
+
+ # -----------------------------------------------------------------------------
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
++# TOO-LONG: aliases=["update_gherkin_languages"])
+ namespace = Collection()
+-namespace.add_task(update_gherkin_languages)
++namespace.add_task(update_gherkin)
+ namespace.configure({})
diff --git a/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
new file mode 100644
index 000000000..65a4691c6
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
@@ -0,0 +1,322 @@
+From 32a7500d834b1856ad5f7f3af4a19acaef61294d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:10:26 +0200
+Subject: [PATCH] EXAMPLE: Tweak naming to @fixture.behave.no_background (was:
+ .override_background)
+
+---
+ examples/fixture.no_background/README.rst | 110 ++++++++++++++++++
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/no_background.py | 72 ++++++++++++
+ .../features/environment.py | 35 ++++++
+ .../features/example.feature | 18 +++
+ .../features/steps/basic_steps.py | 13 +++
+ .../features/steps/use_steplib_behave4cmd.py | 12 ++
+ 7 files changed, 260 insertions(+)
+ create mode 100644 examples/fixture.no_background/README.rst
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/__init__.py
+ create mode 100644 examples/fixture.no_background/behave_fixture_lib/no_background.py
+ create mode 100644 examples/fixture.no_background/features/environment.py
+ create mode 100644 examples/fixture.no_background/features/example.feature
+ create mode 100644 examples/fixture.no_background/features/steps/basic_steps.py
+ create mode 100644 examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/examples/fixture.no_background/README.rst b/examples/fixture.no_background/README.rst
+new file mode 100644
+index 0000000..4243f10
+--- /dev/null
++++ b/examples/fixture.no_background/README.rst
+@@ -0,0 +1,110 @@
++EXAMPLE: Disable Background Inheritance Mechanism for Scenario
++===============================================================================
++
++:RELATED-TO: #756
++
++This example shows how the Background inheritance mechanism in Gherkin
++can be disabled in ``behave``.
++
++Parts of the recipe:
++
++* features/example.feature (Feature file as example)
++* features/environment.py (glue code and hooks for fixture-tag / fixture)
++* behave_fixture_lib/no_background.py (fixture implementation, workhorse)
++
++
++.. warning:: BEWARE: This shows you how can do it, not that you should do it
++
++ BETTER:
++
++ * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
++ * Split Feature aspects into multiple feature files (if needed)
++ * ... (see issue #756 above)
++
++
++Explanation
++------------------------------------------------------------------------
++
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism by using a fixture / fixture-tag.
++The fixture-tag "@fixture.behave.no_background" marks the
++location in Gherkin (which Scenario) where the fixture should be used
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.no_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++When the feature is executed, you see that:
++
++* First Scenario "Alice": Background steps are inherited and executed first.
++* Second Scenario "Bob": No Background step is executed.
++
++.. code-block:: sh
++
++ $ ../../bin/behave -f plain features/example.feature
++ Feature: Override the Background Inheritance Mechanism in some Scenarios
++ Background:
++
++ Scenario: Alice
++ Given a background step passes ... passed
++ When a step passes ... passed
++ And note that "Background steps are executed here" ... passed
++ FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
++
++ Scenario: Bob
++ Given I need another scenario setup ... passed
++ When another step passes ... passed
++ And note that "NO-BACKGROUND STEPS are executed here" ... passed
++
++ 1 feature passed, 0 failed, 0 skipped
++ 2 scenarios passed, 0 failed, 0 skipped
++ 6 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++The environment file provides the glue code that the fixture is called:
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.no_background import behave_no_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++ }
++
++ # -----------------------------------------------------------------------------
++ # HOOKS:
++ # -----------------------------------------------------------------------------
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++
++.. code-block:: python
++
++ # -- FILE: behave_fixture_lib/no_background.py (fixture implementation)
++ from behave import fixture
++
++ @fixture(name="fixture.behave.no_background")
++ def behave_no_background(ctx):
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
+diff --git a/examples/fixture.no_background/behave_fixture_lib/__init__.py b/examples/fixture.no_background/behave_fixture_lib/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/examples/fixture.no_background/behave_fixture_lib/no_background.py b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+new file mode 100644
+index 0000000..47bd0b5
+--- /dev/null
++++ b/examples/fixture.no_background/behave_fixture_lib/no_background.py
+@@ -0,0 +1,72 @@
++# -*- coding: UTF-8 -*-
++# RELATED-TO: #756
++"""
++Example code how to provide a behave fixture to disable the
++background inheritance mechanism.
++
++.. code-block:: gherkin
++
++ # -- FILE: features/example.feature
++ Feature: Show how @fixture.behave.override_background is used
++
++ Background:
++ Given a background step
++
++ Scenario: Alice
++ When a step passes
++ And note that "Background steps are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
++
++.. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave_fixture_lib.override_background import behave_override_background
++ from behave.fixture import use_fixture_by_tag
++
++ # -- FIXTURE REGISTRY:
++ fixture_registry = {
++ "fixture.behave.no_background": behave_override_background,
++ }
++
++ def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
++"""
++
++from __future__ import absolute_import, print_function
++from behave import fixture
++
++
++# -----------------------------------------------------------------------------
++# BEHAVE FIXTURES:
++# -----------------------------------------------------------------------------
++@fixture(name="fixture.behave.ono_background")
++def behave_no_background(ctx):
++ """Override the Background inherintance mechanism.
++ If a Feature / Rule Background exists in a Feature,
++ all contained Scenarios inherit the Background's steps.
++
++ This fixture disables this mechanism.
++ The tagged Gherkin element will no longer inherit the background steps.
++
++ :param ctx: Context object to use (during a test run).
++ """
++ # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
++ current_scenario = ctx.scenario
++ if current_scenario:
++ print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % current_scenario.name)
++ current_scenario.use_background = False
++
++
++# -----------------------------------------------------------------------------
++# MODULE SPECIFIC:
++# -----------------------------------------------------------------------------
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
+diff --git a/examples/fixture.no_background/features/environment.py b/examples/fixture.no_background/features/environment.py
+new file mode 100644
+index 0000000..18857b9
+--- /dev/null
++++ b/examples/fixture.no_background/features/environment.py
+@@ -0,0 +1,35 @@
++# -*- coding: UTF-8 -*-
++# -- FILE: features/environment.py
++import os.path
++import sys
++
++# -----------------------------------------------------------------------------
++# PYTHON PATH SETUP:
++# -----------------------------------------------------------------------------
++HERE = os.path.dirname(__file__)
++TOPA = os.path.abspath(os.path.join(HERE, ".."))
++
++def setup_python_path():
++ sys.path.insert(0, TOPA)
++
++setup_python_path()
++
++# -----------------------------------------------------------------------------
++# NORMAL PART:
++# -----------------------------------------------------------------------------
++from behave_fixture_lib.no_background import behave_no_background
++from behave.fixture import use_fixture_by_tag
++
++# -- FIXTURE REGISTRY:
++fixture_registry = {
++ "fixture.behave.no_background": behave_no_background,
++}
++
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_tag(context, tag):
++ if tag.startswith("fixture."):
++ return use_fixture_by_tag(tag, context, fixture_registry)
++
+diff --git a/examples/fixture.no_background/features/example.feature b/examples/fixture.no_background/features/example.feature
+new file mode 100644
+index 0000000..2025716
+--- /dev/null
++++ b/examples/fixture.no_background/features/example.feature
+@@ -0,0 +1,18 @@
++Feature: Disable the Background Inheritance Mechanism in some Scenarios
++
++ . BEWARE:
++ . This is only an example how this can be done (PROOF-OF-CONCEPT).
++ . This is not an example that you should do this !!!
++
++ Background:
++ Given a background step passes
++
++ Scenario: Alice
++ When a step passes
++ And note that "BACKGROUND STEPS are executed here"
++
++ @fixture.behave.no_background
++ Scenario: Bob
++ Given I need another scenario setup
++ When another step passes
++ And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.no_background/features/steps/basic_steps.py b/examples/fixture.no_background/features/steps/basic_steps.py
+new file mode 100644
+index 0000000..34f2107
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/basic_steps.py
+@@ -0,0 +1,13 @@
++from behave import given, step
++
++# @step(u'{word} step passes')
++# def step_passes_with_word(context, word):
++# pass
++
++@step(u'{word} background step passes')
++def step_background_step_passes(context, word):
++ pass
++
++@given(u'I need {word} scenario setup')
++def step_given_i_need_scenario_setup(context, word):
++ pass
+diff --git a/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+new file mode 100644
+index 0000000..bc32a32
+--- /dev/null
++++ b/examples/fixture.no_background/features/steps/use_steplib_behave4cmd.py
+@@ -0,0 +1,12 @@
++# -*- coding: utf-8 -*-
++"""
++Use behave4cmd0 step library (predecessor of behave4cmd).
++"""
++
++from __future__ import absolute_import
++
++# -- REGISTER-STEPS FROM STEP-LIBRARY:
++# import behave4cmd0.__all_steps__
++# import behave4cmd0.failing_steps
++import behave4cmd0.passing_steps
++import behave4cmd0.note_steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
new file mode 100644
index 000000000..1d55043e4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
@@ -0,0 +1,64 @@
+From 8ef1ca5016cfd4e417fc8b6e8becf1b1d1ff0767 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 9 Jul 2019 08:20:25 +0200
+Subject: [PATCH] EXAMPLE: Cleanup Gherkin v6 README
+
+---
+ examples/gherkin_v6/README.rst | 23 +++++++++++--------
+ .../features/steps/passing_steps.py | 11 +++++++++
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+ create mode 100644 examples/gherkin_v6/features/steps/passing_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+index 58199dd..99af1c5 100644
+--- a/examples/gherkin_v6/README.rst
++++ b/examples/gherkin_v6/README.rst
+@@ -2,17 +2,22 @@ Gherkin v6 Examples
+ =============================================================================
+
+
+-SCRATCHPAD: Problems
+------------------------------------------------------------------------------
++Provides example(s) of Gherkin v6 additions:
+
+-- SummaryReporter: Shows wrong counts when Rules are present::
++* Rule concept
++* New aliases for Gherkin keywords (Scenario, ScenarioOutline)
+
+- ...
+- 0 features passed, 0 failed, 1 skipped XXX
+- 3 rules passed, 0 failed, 0 skipped
+- 5 scenarios passed, 0 failed, 0 skipped
+- 13 steps passed, 0 failed, 0 skipped, 0 undefined
++Rule functionality:
+
++* A Rule is a scenario container similar to a Feature
++* A Feature may contain many Rules
++* A Rule may not contain other Rules
++* A Rule may contain a Background (and inherits its Feature Background)
++* A Rule inherits its Feature Background if it has no Background
++* A Rule may contain many Scenarios and/or ScenarioOutlines
++* A Rule may have tags
+
+-- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++New keyword aliases:
+
++* "Scenario Template" for "Scenario Outline"
++* "Example" for "Scenario"
+diff --git a/examples/gherkin_v6/features/steps/passing_steps.py b/examples/gherkin_v6/features/steps/passing_steps.py
+new file mode 100644
+index 0000000..2714cb1
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/passing_steps.py
+@@ -0,0 +1,11 @@
++# -*- coding: UTF-8 -*-
++
++from behave import step
++
++@step(u'{word} step passes')
++def step_passes(ctx, word):
++ pass
++
++@step(u'{word} step fails')
++def step_fails(ctx, word):
++ assert False, "XFAIL-STEP: {0} step fails".format(word)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
new file mode 100644
index 000000000..0307246cf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
@@ -0,0 +1,1510 @@
+From 23a80a765e10dad0d363631066c17bd1131e8015 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 10 Jul 2019 22:38:13 +0200
+Subject: [PATCH] Improve support for feature.background inheritance for
+ rule.background.
+
+---
+ .gitignore | 3 +
+ behave/model.py | 230 ++++++++++--
+ behave/parser.py | 9 +-
+ .../fixture.override_background/README.rst | 116 ------
+ .../behave_fixture_lib/__init__.py | 0
+ .../behave_fixture_lib/override_background.py | 80 -----
+ .../features/environment.py | 35 --
+ .../features/example.feature | 18 -
+ .../features/steps/basic_steps.py | 13 -
+ .../features/steps/use_steplib_behave4cmd.py | 12 -
+ setup.py | 1 +
+ tests/unit/test_model.py | 117 +-----
+ tests/unit/test_model2.py | 4 -
+ tests/unit/test_model_core.py | 116 +++++-
+ tests/unit/test_parser_gherkin_v6.py | 339 +++++++++++++++++-
+ 15 files changed, 645 insertions(+), 448 deletions(-)
+ delete mode 100644 examples/fixture.override_background/README.rst
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/__init__.py
+ delete mode 100644 examples/fixture.override_background/behave_fixture_lib/override_background.py
+ delete mode 100644 examples/fixture.override_background/features/environment.py
+ delete mode 100644 examples/fixture.override_background/features/example.feature
+ delete mode 100644 examples/fixture.override_background/features/steps/basic_steps.py
+ delete mode 100644 examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+
+diff --git a/.gitignore b/.gitignore
+index 6196a6d..9c5c33d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -7,6 +7,9 @@ build/
+ dist/
+ __pycache__/
+ __WORKDIR__/
++__*/
++__*.txt
++__*.rst
+ _build/
+ _WORKSPACE/
+ reports/
+diff --git a/behave/model.py b/behave/model.py
+index 7fc534a..69f38ab 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -29,6 +29,36 @@ else:
+ import traceback
+
+
++# ---------------------------------------------------------------------------
++# MODEL UTILITIES:
++# ---------------------------------------------------------------------------
++def reset_steps(steps):
++ for step in steps:
++ step.reset()
++ return steps
++
++
++def copy_steps(steps):
++ """Copy steps; needed if steps should be used in multiple run contexts.
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return [copy.copy(step) for step in steps]
++
++
++def copy_and_reset_steps(steps):
++ """Copy steps and reset each step (status, duration, etc.)
++
++ :param steps: List of steps to copy.
++ :return: List of copied steps.
++ """
++ return reset_steps(copy_steps(steps))
++
++
++# ---------------------------------------------------------------------------
++# MODEL CLASSES:
++# ---------------------------------------------------------------------------
+ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """Abstract base class for model elements
+ that contains the following structure:
+@@ -198,8 +228,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ if skipped:
+ return Status.skipped
+- else:
+- return Status.passed
++ # -- OTHERWISE:
++ return Status.passed
+
+ @property
+ def duration(self):
+@@ -230,7 +260,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ rule = run_item
+ if with_rules:
+ all_scenarios.append(rule)
+- all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ scenarios = rule.walk_scenarios(with_outlines=with_outlines)
++ all_scenarios.extend(scenarios)
+ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+@@ -241,6 +272,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ all_scenarios.append(run_item)
+ return all_scenarios
+
++ def iter_scenarios(self):
++ return iter(self.walk_scenarios())
++
++ def iter_scenario_outlines(self):
++ return iter([x for x in self.walk_scenarios(with_outlines=True)
++ if isinstance(x, ScenarioOutline)])
++
++ def iter_rules(self):
++ return iter([x for x in self.run_items if isinstance(x, Rule)])
++
+ def should_run(self, config=None):
+ """
+ Determines if this Feature (and its scenarios) should run.
+@@ -312,7 +353,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param runner: Runner to use.
+ :return: True, if test-run failed.
+ """
+- # pylint: disable=too-many-branches
++ # pylint: disable=too-many-branches, too-many-locals, too-many-statements
+ # MAYBE: self.reset()
+ self.clear_status()
+ self.hook_failed = False
+@@ -387,7 +428,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+@@ -509,16 +550,28 @@ class Feature(ScenarioContainer):
+ def _setup_context_for_run(self, context):
+ context.feature = self
+
++ def add_background(self, background):
++ self.background = background
++ self.background.parent = self
++
+ def add_rule(self, rule):
+- """Add a rule to this feature."""
++ """Add a rule to this feature (supported in: Gherkin v6).
++
++ .. versionadded: 1.2.7
++ """
+ feature = self
+ rule.parent = feature
+ rule.feature = feature
+- if not rule.background:
+- # -- MAYBE: Inherit feature.background if the rule has no background.
+- rule.background = self.background
+ self.rules.append(rule)
+ self.run_items.append(rule)
++ if self.background:
++ # -- ENSURE: Rule inherits feature.background.
++ if not rule.background:
++ # -- ENSURE: Rule has a default background.
++ # Necessary to inherit feature.background (or disable it).
++ rule_default_background = Background(rule.filename, rule.line)
++ rule.add_background(rule_default_background)
++ rule.background.inherited_background = self.background
+
+
+ class Rule(ScenarioContainer):
+@@ -630,6 +683,7 @@ class Rule(ScenarioContainer):
+ description, scenarios, background)
+ self.parent = parent
+ self.feature = parent
++ self._use_background_inheritance = True
+
+ def _setup_context_for_run(self, context):
+ context.rule = self
+@@ -638,10 +692,43 @@ class Rule(ScenarioContainer):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+
++ def add_background(self, background, inherited=None):
++ if inherited is None:
++ feature = self.feature or self.parent
++ inherited = feature.background
++
++ self.background = background
++ self.background.inherited_background = inherited
++ self.background.use_inheritance = self.use_background_inheritance
++ self.background.parent = self
++ # -- ENSURE: Normally background is added before scenarios.
++ for scenario in self.walk_scenarios():
++ scenario.background = self.background
++
++ @property
++ def use_background_inheritance(self):
++ return self._use_background_inheritance
++
++ @use_background_inheritance.setter
++ def use_background_inheritance(self, value):
++ self._use_background_inheritance = value
++ if self.background:
++ self.background.use_inheritance = value
++
+
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
++ Behaviour:
++
++ * Each scenario of a scenario container (Feature, Rule)
++ inherits the Background of its scenario container
++ * Background steps in a scenario are executed before scenario steps
++ * Rule Background inherits the Feature Background (outer background) if any
++ * Inherited Background steps are used/executed first
++ * Optionally, background inheritance can be disabled
++ (normally: by using a fixture/fixture-tag)
++
+ The attributes are:
+
+ .. attribute:: keyword
+@@ -679,23 +766,65 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
+- # TODO: Background inheritance
+- # Rule.background should inherit its Feature.background steps (if available)
+- # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
+- # Rule may override background inheritance mechanism
+ type = "background"
+
+- def __init__(self, filename, line, keyword, name, steps=None, description=None):
++ def __init__(self, filename, line, keyword=u"Background", name=u"",
++ steps=None, description=None):
+ super(Background, self).__init__(filename, line, keyword, name)
+ self.description = description or []
+ self.steps = steps or []
++ self.inherited_background = None
++ self._inherited_steps = None
++ self._use_inheritance = True
+
+- def __repr__(self):
+- return '<Background "%s">' % self.name
++ @property
++ def use_inheritance(self):
++ """Indicates if this Background should inherit from an outer Background.
++ Background inheritance mechanism is enabled (per default).
++ Optionally, this mechanism can be disabled (or overridden).
+
+- def __iter__(self):
++ :return: Current background inheritance state (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_inheritance
++
++ @use_inheritance.setter
++ def use_inheritance(self, value):
++ """Enable/disable background inheritance mechanism for this Background.
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: inherited_steps are reinitialized (later).
++ self._use_inheritance = bool(value)
++ self._inherited_steps = None
++
++ @property
++ def inherited_steps(self):
++ # versionadded:: 1.2.7
++ if self._inherited_steps is None:
++ # -- LAZY-INIT: Support enable/disable the inheritance mechanism.
++ steps = []
++ if self.inherited_background and self._use_inheritance:
++ steps = copy_and_reset_steps(self.inherited_background.steps)
++ self._inherited_steps = steps
++ return self._inherited_steps
++
++ def iter_steps(self):
++ """Returns iterator to all steps, including inherited steps (if any).
++
++ .. versionadded:: 1.2.7
++ """
++ if self.inherited_steps:
++ return itertools.chain(self.inherited_steps, self.steps)
+ return iter(self.steps)
+
++ @property
++ def all_steps(self):
++ return self.iter_steps()
++
+ @property
+ def duration(self):
+ duration = 0
+@@ -703,6 +832,12 @@ class Background(BasicStatement, Replayable):
+ duration += step.duration
+ return duration
+
++ def __repr__(self):
++ return '<Background "%s">' % self.name
++
++ def __iter__(self):
++ return self.iter_steps()
++
+
+ class Scenario(TagAndStatusStatement, Replayable):
+ """A `scenario`_ parsed from a *feature file*.
+@@ -799,6 +934,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ self.feature = None # REFER-TO: owner=Feature
+ self.hook_failed = False
+ self._background_steps = None
++ self._use_background = True
+ self._row = None
+ self.was_dry_run = False
+
+@@ -813,6 +949,27 @@ class Scenario(TagAndStatusStatement, Replayable):
+ for step in self.all_steps:
+ step.reset()
+
++ @property
++ def use_background(self):
++ """Indicates if the background is/would be used (if any exists).
++ NOTE: The Background (steps) are normally used.
++
++ .. versionadded:: 1.2.7
++ """
++ return self._use_background
++
++ @use_background.setter
++ def use_background(self, value):
++ """Enable/disable the usage of the background (steps).
++
++ :param value: New value (as bool).
++
++ .. versionadded:: 1.2.7
++ """
++ # -- ENSURE: background_steps are reinitialized.
++ self._use_background = value
++ self._background_steps = None
++
+ @property
+ def background_steps(self):
+ """Provide background steps if feature/rule has a background.
+@@ -828,24 +985,29 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # Each scenario needs own background.steps.
+ # Otherwise, background step status of the last-run scenario is used.
+ steps = []
+- if self.background:
+- steps = [copy.copy(step) for step in self.background.steps]
++ if self.background and self.use_background:
++ steps = copy_and_reset_steps(self.background.all_steps)
+ self._background_steps = steps
+ return self._background_steps
+
+- @property
+- def all_steps(self):
+- """Returns iterator to all steps, including background steps if any."""
++ def iter_steps(self):
++ """Returns iterator to all steps, including background steps if any.
++
++ .. versionadded:: 1.2.7
++ """
+ if self.background is not None:
+ return itertools.chain(self.background_steps, self.steps)
+- else:
+- return iter(self.steps)
++ return iter(self.steps)
++
++ @property
++ def all_steps(self):
++ return self.iter_steps()
+
+ def __repr__(self):
+ return '<Scenario "%s">' % self.name
+
+ def __iter__(self):
+- return self.all_steps
++ return self.iter_steps()
+
+ def compute_status(self):
+ """Compute the status of the scenario from its steps
+@@ -862,9 +1024,8 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
+ # Undefined steps should not cause failed scenario.
+ return Status.untested
+- else:
+- # -- NORMALLY: Undefined steps cause failed scenario.
+- return Status.failed
++ # -- NORMALLY: Undefined steps cause failed scenario.
++ return Status.failed
+ elif step.status != Status.passed:
+ # pylint: disable=line-too-long
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
+@@ -1029,7 +1190,6 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # BUT: Detect all remaining undefined steps.
+ step.status = Status.skipped
+ if dry_run_scenario:
+- # pylint: disable=redefined-variable-type
+ step.status = Status.untested
+ found_step_match = runner.step_registry.find_match(step)
+ if not found_step_match:
+@@ -1067,7 +1227,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors.
+ try:
+ runner.context._pop() # pylint: disable=protected-access
+- except Exception:
++ except Exception: # pylint: disable=broad-except
+ self.set_status(Status.failed)
+ failed = True
+
+@@ -1176,9 +1336,9 @@ class ScenarioOutlineBuilder(object):
+ placeholder = u"<%s>" % name
+ for i, cell in enumerate(new_step.table.headings):
+ new_step.table.headings[i] = cell.replace(placeholder, value)
+- for row in new_step.table:
+- for i, cell in enumerate(row.cells):
+- row.cells[i] = cell.replace(placeholder, value)
++ for step_row in new_step.table:
++ for i, cell in enumerate(step_row.cells):
++ step_row.cells[i] = cell.replace(placeholder, value)
+ return new_step
+
+ def build_scenarios(self, scenario_outline):
+@@ -1640,7 +1800,6 @@ class Step(BasicStatement, Replayable):
+ match.run(runner.context)
+ if self.status == Status.untested:
+ # -- NOTE: Executed step may have skipped scenario and itself.
+- # pylint: disable=redefined-variable-type
+ self.status = Status.passed
+ except KeyboardInterrupt as e:
+ runner.aborted = True
+@@ -1815,8 +1974,7 @@ class Table(Replayable):
+ """
+ if self.has_column(column_name):
+ return self.get_column_index(column_name)
+- else:
+- return self.add_column(column_name)
++ return self.add_column(column_name)
+
+ def __repr__(self):
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
+diff --git a/behave/parser.py b/behave/parser.py
+index 993c9dc..520f678 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -249,7 +249,6 @@ class Parser(object):
+ self.rule = rule
+ self.scenario_container = rule
+ self.statement = rule
+- # MAYBE: self.background = None
+ self.feature.add_rule(self.statement)
+ # -- RESET STATE:
+ self.tags = []
+@@ -258,11 +257,15 @@ class Parser(object):
+ if self.tags:
+ msg = u"Background supports no tags: @%s" % (u" @".join(self.tags))
+ raise ParserError(msg, self.line, self.filename, line)
++ elif self.scenario_container and self.scenario_container.background:
++ if self.scenario_container.background.steps:
++ # -- HINT: Rule may have default background w/o steps.
++ msg = u"Second Background (can have only one)"
++ raise ParserError(msg, self.line, self.filename, line)
+ name = line[len(keyword) + 1:].strip()
+ background = model.Background(self.filename, self.line, keyword, name)
++ self.scenario_container.add_background(background)
+ self.statement = background
+- self.scenario_container.background = background
+- # OLD: self.feature.background = self.statement
+
+ def _build_scenario_statement(self, keyword, line):
+ name = line[len(keyword) + 1:].strip()
+diff --git a/examples/fixture.override_background/README.rst b/examples/fixture.override_background/README.rst
+deleted file mode 100644
+index 9c150cc..0000000
+--- a/examples/fixture.override_background/README.rst
++++ /dev/null
+@@ -1,116 +0,0 @@
+-EXAMPLE: Override / Disable Background Inheritance Mechanism for Scenario
+-===============================================================================
+-
+-:RELATED-TO: #756
+-
+-This example shows how the Background inheritance mechanism in Gherkin
+-can be disabled in ``behave``.
+-
+-Parts of the recipe:
+-
+-* features/example.feature (Feature file as example)
+-* features/environment.py (glue code and hooks for fixture-tag / fixture)
+-* behave_fixture_lib/override_background.py (fixture implementation, workhorse)
+-
+-
+-.. warning:: BEWARE: This shows you how can do it, not that you should do it
+-
+- BETTER:
+-
+- * Use Rules to group Scenarios, each with its own Background (in Gherkin v6)
+- * Split Feature aspects into multiple feature files (if needed)
+- * ... (see issue #756 above)
+-
+-
+-Explanation
+-------------------------------------------------------------------------
+-
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism by using a fixture / fixture-tag.
+-The fixture-tag "@ixture.behave.override_background" marks the
+-location in Gherkin (which Scenario) where the fixture should be used
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-When the feature is executed, you see that:
+-
+-* First Scenario "Alice": Background steps are inherited and executed first.
+-* Second Scenario "Bob": No Background step is executed.
+-
+-.. code-block:: sh
+-
+- $ ../../bin/behave -f plain features/example.feature
+- Feature: Override the Background Inheritance Mechanism in some Scenarios
+- Background:
+-
+- Scenario: Alice
+- Given a background step passes ... passed
+- When a step passes ... passed
+- And note that "Background steps are executed here" ... passed
+- FIXTURE-HINT: DISABLE-BACKGROUND FOR: Bob
+-
+- Scenario: Bob
+- Given I need another scenario setup ... passed
+- When another step passes ... passed
+- And note that "NO-BACKGROUND STEPS are executed here" ... passed
+-
+- 1 feature passed, 0 failed, 0 skipped
+- 2 scenarios passed, 0 failed, 0 skipped
+- 6 steps passed, 0 failed, 0 skipped, 0 undefined
+-
+-
+-The environment file provides the glue code that the fixture is called:
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- # -----------------------------------------------------------------------------
+- # HOOKS:
+- # -----------------------------------------------------------------------------
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-
+-.. code-block:: python
+-
+- # -- FILE: behave_fixture_lib/override_background.py (fixture implementation)
+- from behave import fixture
+-
+- @fixture(name="fixture.behave.override_background")
+- def behave_override_background(ctx):
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+- # -----------------------------------------------------------------------------
+- # BEHAVE UTILITY:
+- # -----------------------------------------------------------------------------
+- def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+diff --git a/examples/fixture.override_background/behave_fixture_lib/__init__.py b/examples/fixture.override_background/behave_fixture_lib/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/examples/fixture.override_background/behave_fixture_lib/override_background.py b/examples/fixture.override_background/behave_fixture_lib/override_background.py
+deleted file mode 100644
+index 6c572cf..0000000
+--- a/examples/fixture.override_background/behave_fixture_lib/override_background.py
++++ /dev/null
+@@ -1,80 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# RELATED-TO: #756
+-"""
+-Example code how to provide a behave fixture to disable the
+-background inheritance mechanism.
+-
+-.. code-block:: gherkin
+-
+- # -- FILE: features/example.feature
+- Feature: Show how @fixture.behave.override_background is used
+-
+- Background:
+- Given a background step
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+-
+-.. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave_fixture_lib.override_background import behave_override_background
+- from behave.fixture import use_fixture_by_tag
+-
+- # -- FIXTURE REGISTRY:
+- fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+- }
+-
+- def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+-"""
+-
+-from __future__ import absolute_import, print_function
+-from behave import fixture
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE FIXTURES:
+-# -----------------------------------------------------------------------------
+-@fixture(name="fixture.behave.override_background")
+-def behave_override_background(ctx):
+- """Override the Background inherintance mechanism.
+- If a Feature / Rule Background exists in a Feature,
+- all contained Scenarios inherit the Background's steps.
+-
+- This fixture disables this mechanism.
+- The tagged Gherkin element will no longer inherit the background steps.
+-
+- :param ctx: Context object to use (during a test run).
+- """
+- # -- SETUP-PART-ONLY: Disable background inheritance (for scenarios only).
+- current_scenario = ctx.scenario
+- if current_scenario:
+- behave_disable_background_inheritance_for_scenario(current_scenario)
+-
+-
+-# -----------------------------------------------------------------------------
+-# BEHAVE UTILITY:
+-# -----------------------------------------------------------------------------
+-def behave_disable_background_inheritance_for_scenario(scenario):
+- print("FIXTURE-HINT: DISABLE-BACKGROUND FOR: %s" % scenario.name)
+- scenario.background = None
+- # scenario._background_steps = []
+-
+-
+-# -----------------------------------------------------------------------------
+-# MODULE SPECIFIC:
+-# -----------------------------------------------------------------------------
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+diff --git a/examples/fixture.override_background/features/environment.py b/examples/fixture.override_background/features/environment.py
+deleted file mode 100644
+index 7a4b735..0000000
+--- a/examples/fixture.override_background/features/environment.py
++++ /dev/null
+@@ -1,35 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-# -- FILE: features/environment.py
+-import os.path
+-import sys
+-
+-# -----------------------------------------------------------------------------
+-# PYTHON PATH SETUP:
+-# -----------------------------------------------------------------------------
+-HERE = os.path.dirname(__file__)
+-TOPA = os.path.abspath(os.path.join(HERE, ".."))
+-
+-def setup_python_path():
+- sys.path.insert(0, TOPA)
+-
+-setup_python_path()
+-
+-# -----------------------------------------------------------------------------
+-# NORMAL PART:
+-# -----------------------------------------------------------------------------
+-from behave_fixture_lib.override_background import behave_override_background
+-from behave.fixture import use_fixture_by_tag
+-
+-# -- FIXTURE REGISTRY:
+-fixture_registry = {
+- "fixture.behave.overide_background": behave_override_background,
+-}
+-
+-
+-# -----------------------------------------------------------------------------
+-# HOOKS:
+-# -----------------------------------------------------------------------------
+-def before_tag(context, tag):
+- if tag.startswith("fixture."):
+- return use_fixture_by_tag(tag, context, fixture_registry)
+-
+diff --git a/examples/fixture.override_background/features/example.feature b/examples/fixture.override_background/features/example.feature
+deleted file mode 100644
+index 5ddd874..0000000
+--- a/examples/fixture.override_background/features/example.feature
++++ /dev/null
+@@ -1,18 +0,0 @@
+-Feature: Override the Background Inheritance Mechanism in some Scenarios
+-
+- . BEWARE:
+- . This is only an example how this can be done (PROOF-OF-CONCEPT).
+- . This is not an example that you should do this !!!
+-
+- Background:
+- Given a background step passes
+-
+- Scenario: Alice
+- When a step passes
+- And note that "Background steps are executed here"
+-
+- @fixture.behave.overide_background
+- Scenario: Bob
+- Given I need another scenario setup
+- When another step passes
+- And note that "NO-BACKGROUND STEPS are executed here"
+diff --git a/examples/fixture.override_background/features/steps/basic_steps.py b/examples/fixture.override_background/features/steps/basic_steps.py
+deleted file mode 100644
+index 34f2107..0000000
+--- a/examples/fixture.override_background/features/steps/basic_steps.py
++++ /dev/null
+@@ -1,13 +0,0 @@
+-from behave import given, step
+-
+-# @step(u'{word} step passes')
+-# def step_passes_with_word(context, word):
+-# pass
+-
+-@step(u'{word} background step passes')
+-def step_background_step_passes(context, word):
+- pass
+-
+-@given(u'I need {word} scenario setup')
+-def step_given_i_need_scenario_setup(context, word):
+- pass
+diff --git a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py b/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
+deleted file mode 100644
+index bc32a32..0000000
+--- a/examples/fixture.override_background/features/steps/use_steplib_behave4cmd.py
++++ /dev/null
+@@ -1,12 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Use behave4cmd0 step library (predecessor of behave4cmd).
+-"""
+-
+-from __future__ import absolute_import
+-
+-# -- REGISTER-STEPS FROM STEP-LIBRARY:
+-# import behave4cmd0.__all_steps__
+-# import behave4cmd0.failing_steps
+-import behave4cmd0.passing_steps
+-import behave4cmd0.note_steps
+diff --git a/setup.py b/setup.py
+index cea4392..8de3ec0 100644
+--- a/setup.py
++++ b/setup.py
+@@ -131,6 +131,7 @@ setup(
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
++ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
+index c1fc424..21d6c27 100644
+--- a/tests/unit/test_model.py
++++ b/tests/unit/test_model.py
+@@ -8,7 +8,7 @@ from mock import Mock, patch
+ import six
+ from six.moves import range # pylint: disable=redefined-builtin
+ from six.moves import zip # pylint: disable=redefined-builtin
+-from behave.model_core import FileLocation, Status
++from behave.model_core import Status
+ from behave.model import Feature, Scenario, ScenarioOutline, Step
+ from behave.model import Table, Row
+ from behave.matchers import NoMatch
+@@ -20,19 +20,12 @@ from behave import step_registry
+
+ if six.PY2:
+ # pylint: disable=unused-import
+- import traceback2 as traceback
+ traceback_modname = "traceback2"
+ else:
+ # pylint: disable=unused-import
+- import traceback
+ traceback_modname = "traceback"
+
+
+-
+-# -- CONVENIENCE-ALIAS:
+-_text = six.text_type
+-
+-
+ class TestFeatureRun(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+@@ -769,111 +762,3 @@ class TestModelRow(unittest.TestCase):
+ assert data1["name"] == u"Alice"
+ assert data1["sex"] == u"female"
+ assert data1["age"] == u"12"
+-
+-
+-class TestFileLocation(unittest.TestCase):
+- # pylint: disable=invalid-name
+- ordered_locations1 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 5),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/alice.feature", 11),
+- FileLocation("features/alice.feature", 100),
+- ]
+- ordered_locations2 = [
+- FileLocation("features/alice.feature", 1),
+- FileLocation("features/alice.feature", 10),
+- FileLocation("features/bob.feature", 5),
+- FileLocation("features/charly.feature", None),
+- FileLocation("features/charly.feature", 0),
+- FileLocation("features/charly.feature", 100),
+- ]
+- same_locations = [
+- (FileLocation("alice.feature"),
+- FileLocation("alice.feature", None),
+- ),
+- (FileLocation("alice.feature", 10),
+- FileLocation("alice.feature", 10),
+- ),
+- (FileLocation("features/bob.feature", 11),
+- FileLocation("features/bob.feature", 11),
+- ),
+- ]
+-
+- def test_compare_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 == value2
+-
+- def test_compare_equal_with_string(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_compare_not_equal(self):
+- for value1, value2 in self.same_locations:
+- assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 != value2
+-
+- def test_compare_less_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_less_than_with_string(self):
+- locations = self.ordered_locations2
+- for value1, value2 in zip(locations, locations[1:]):
+- if value1.filename == value2.filename:
+- continue
+- assert value1 < value2.filename, \
+- "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
+- assert value1.filename < value2, \
+- "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
+-
+- def test_compare_greater_than(self):
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_compare_less_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 == value2
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
+- assert value1 != value2
+-
+- def test_compare_greater_or_equal(self):
+- for value1, value2 in self.same_locations:
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 == value1
+-
+- for locations in [self.ordered_locations1, self.ordered_locations2]:
+- for value1, value2 in zip(locations, locations[1:]):
+- assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
+- assert value2 != value1
+-
+- def test_filename_should_be_same_as_self(self):
+- for location in self.ordered_locations2:
+- assert location == location.filename
+- assert location.filename == location
+-
+- def test_string_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u"%s:%s" % (location.filename, location.line)
+- if location.line is None:
+- expected = location.filename
+- assert six.text_type(location) == expected
+-
+- def test_repr_conversion(self):
+- for location in self.ordered_locations2:
+- expected = u'<FileLocation: filename="%s", line=%s>' % \
+- (location.filename, location.line)
+- actual = repr(location)
+- assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_model2.py b/tests/unit/test_model2.py
+index 7884b90..a86b80e 100644
+--- a/tests/unit/test_model2.py
++++ b/tests/unit/test_model2.py
+@@ -35,10 +35,6 @@ def step_to_text(step, indentation=" "):
+ return step_text.rstrip()
+
+
+-# -- PYTEST MARKERS/ANNOTATIONS:
+-not_implemented_yet = pytest.mark.skip("NOT-IMPLEMENTED-YET")
+-
+-
+ # ----------------------------------------------------------------------------
+ # TEST SUITE:
+ # ----------------------------------------------------------------------------
+diff --git a/tests/unit/test_model_core.py b/tests/unit/test_model_core.py
+index b5f20c4..3cb5efa 100644
+--- a/tests/unit/test_model_core.py
++++ b/tests/unit/test_model_core.py
+@@ -4,10 +4,16 @@
+ """
+
+ from __future__ import print_function
+-from behave.model_core import Status
++import six
++from behave.model_core import Status, FileLocation
+ import pytest
+
+
++# -- CONVENIENCE-ALIAS:
++_text = six.text_type
++
++
++
+ # -----------------------------------------------------------------------------
+ # TESTS:
+ # -----------------------------------------------------------------------------
+@@ -54,3 +60,111 @@ class TestStatus(object):
+ def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name):
+ with pytest.raises(LookupError):
+ Status.from_name(unknown_name)
++
++
++class TestFileLocation(object):
++ # pylint: disable=invalid-name
++ ordered_locations1 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 5),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/alice.feature", 11),
++ FileLocation("features/alice.feature", 100),
++ ]
++ ordered_locations2 = [
++ FileLocation("features/alice.feature", 1),
++ FileLocation("features/alice.feature", 10),
++ FileLocation("features/bob.feature", 5),
++ FileLocation("features/charly.feature", None),
++ FileLocation("features/charly.feature", 0),
++ FileLocation("features/charly.feature", 100),
++ ]
++ same_locations = [
++ (FileLocation("alice.feature"),
++ FileLocation("alice.feature", None),
++ ),
++ (FileLocation("alice.feature", 10),
++ FileLocation("alice.feature", 10),
++ ),
++ (FileLocation("features/bob.feature", 11),
++ FileLocation("features/bob.feature", 11),
++ ),
++ ]
++
++ def test_compare_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 == value2
++
++ def test_compare_equal_with_string(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_compare_not_equal(self):
++ for value1, value2 in self.same_locations:
++ assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 != value2
++
++ def test_compare_less_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_less_than_with_string(self):
++ locations = self.ordered_locations2
++ for value1, value2 in zip(locations, locations[1:]):
++ if value1.filename == value2.filename:
++ continue
++ assert value1 < value2.filename, \
++ "FAILED: %s < %s" % (_text(value1), _text(value2.filename))
++ assert value1.filename < value2, \
++ "FAILED: %s < %s" % (_text(value1.filename), _text(value2))
++
++ def test_compare_greater_than(self):
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_compare_less_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 == value2
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2))
++ assert value1 != value2
++
++ def test_compare_greater_or_equal(self):
++ for value1, value2 in self.same_locations:
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 == value1
++
++ for locations in [self.ordered_locations1, self.ordered_locations2]:
++ for value1, value2 in zip(locations, locations[1:]):
++ assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1))
++ assert value2 != value1
++
++ def test_filename_should_be_same_as_self(self):
++ for location in self.ordered_locations2:
++ assert location == location.filename
++ assert location.filename == location
++
++ def test_string_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u"%s:%s" % (location.filename, location.line)
++ if location.line is None:
++ expected = location.filename
++ assert six.text_type(location) == expected
++
++ def test_repr_conversion(self):
++ for location in self.ordered_locations2:
++ expected = u'<FileLocation: filename="%s", line=%s>' % \
++ (location.filename, location.line)
++ actual = repr(location)
++ assert actual == expected, "FAILED: %s == %s" % (actual, expected)
+diff --git a/tests/unit/test_parser_gherkin_v6.py b/tests/unit/test_parser_gherkin_v6.py
+index 991a57d..43e3d41 100644
+--- a/tests/unit/test_parser_gherkin_v6.py
++++ b/tests/unit/test_parser_gherkin_v6.py
+@@ -227,7 +227,9 @@ Feature: With Rule
+ assert rule1.description == []
+ assert rule1.tags == []
+ assert len(rule1.scenarios) == 1
+- assert rule1.background is feature.background
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) == feature.background.steps
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
+ ("given", "Given", "feature background step 1", None, None),
+ ("when", "When", "feature background step 2", None, None),
+@@ -235,7 +237,7 @@ Feature: With Rule
+ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_background_should_not_inherit_feature_background(self):
++ def test_parses_rule_with_background_inherits_feature_background(self):
+ """If a Rule has no Background,
+ it inherits the Feature's Background (if one exists).
+ """
+@@ -269,13 +271,15 @@ Feature: With Rule
+ assert rule1.background is not None
+ assert rule1.background is not feature.background
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "rule background step 1", None, None),
+- ("when", "When", "rule background step 2", None, None),
++ ("when", "When", "rule background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+- def test_parses_rule_with_empty_background_prevents_inheriting_feature_background(self):
++ def test_parses_rule_with_empty_background_inherits_feature_background(self):
+ """A Rule has empty Background (without any steps) prevents that
+ Feature Background is inherited (if one exists).
+ """
+@@ -308,8 +312,10 @@ Feature: With Rule
+ assert rule1.background is not feature.background
+ assert rule1.background.name == "Rule_R3C.Empty_Background"
+ assert_compare_steps(rule1.scenarios[0].all_steps, [
++ ("given", "Given", "feature background step 1", None, None),
++ ("when", "When", "feature background step 2", None, None),
+ ("given", "Given", "scenario step 1", None, None),
+- ("when", "When", "scenario step 2", None, None),
++ ("when", "When", "scenario step 2", None, None),
+ ])
+
+ def test_parses_rule_with_scenario(self):
+@@ -558,6 +564,7 @@ Feature: With Rule
+ ("when", "When", 'step uses "2"', None, None),
+ ])
+
++ # @check.duplicated
+ def test_parse_background_scenario_and_rules(self):
+ """HINT: Some Scenarios may exist before the first Rule."""
+ text = u'''
+@@ -606,10 +613,10 @@ Feature: With Scenarios and Rules
+ assert scenario1.tags == []
+ assert scenario1.description == []
+ assert_compare_steps(scenario1.all_steps, [
+- ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
+- ("given", "Given", 'scenario_1 step_1', None, None),
+- ("when", "When", 'scenario_1 step_2', None, None),
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"when", u"When", u'feature background step_2', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ (u"when", u"When", u'scenario_1 step_2', None, None),
+ ])
+
+ assert rule1.name == "R1"
+@@ -623,9 +630,11 @@ Feature: With Scenarios and Rules
+ assert rule1_scenario1.parent is rule1
+ assert rule1_scenario1.feature is feature
+ assert_compare_steps(rule1_scenario1.all_steps, [
++ ("given", "Given", 'feature background step_1', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R1 background step_1', None, None),
+ ("given", "Given", 'rule R1 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R1 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R1 scenario_1 step_2', None, None),
+ ])
+
+ assert rule2.name == "R2"
+@@ -633,16 +642,318 @@ Feature: With Scenarios and Rules
+ assert rule2.feature is feature
+ assert rule2.description == []
+ assert rule2.tags == []
+- assert rule2.background is feature.background
++ assert rule2.background is not feature.background
++ assert list(rule2.background.inherited_steps) == list(feature.background.steps)
++ assert list(rule2.background.all_steps) == list(feature.background.steps)
+ assert len(rule2.scenarios) == 1
+ assert rule2_scenario1.name == "R2.Scenario_1"
+ assert rule2_scenario1.parent is rule2
+ assert rule2_scenario1.feature is feature
+ assert_compare_steps(rule2_scenario1.all_steps, [
+ ("given", "Given", 'feature background step_1', None, None),
+- ("when", "When", 'feature background step_2', None, None),
++ ("when", "When", 'feature background step_2', None, None),
+ ("given", "Given", 'rule R2 scenario_1 step_1', None, None),
+- ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ("when", "When", 'rule R2 scenario_1 step_2', None, None),
++ ])
++
++
++# ---------------------------------------------------------------------------
++# TEST SUITE: Verify Feature Background to Rule Background Inheritance
++# ---------------------------------------------------------------------------
++class TestParser4Background(object):
++ """Verify feature.background to rule.background inheritance, etc."""
++
++ def test_parse__norule_scenarios_use_feature_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: With Scenarios and Rules
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Scenarios and Rules"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 1
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ rule1 = feature.rules[0]
++ assert feature.run_items == [scenario1, rule1]
++
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps == feature.background.steps
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__norule_scenarios_with_disabled_background(self):
++ """AFFECTED: Scenarios outside of rules (before first rule)."""
++ text = u'''
++ Feature: Scenario with disabled background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.disable_background
++ Scenario: Scenario_1
++ Given scenario_1 step_1
++
++ Scenario: Scenario_2
++ Given scenario_2 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Scenario with disabled background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 2
++ assert len(feature.run_items) == 2
++
++ scenario1 = feature.scenarios[0]
++ scenario2 = feature.scenarios[1]
++ assert feature.run_items == [scenario1, scenario2]
++
++ scenario1.use_background = False # -- FIXTURE-EFFECT (simulated)
++ assert scenario1.name == "Scenario_1"
++ assert scenario1.background is feature.background
++ assert scenario1.background_steps != feature.background.steps
++ assert scenario1.background_steps == []
++ assert_compare_steps(scenario1.all_steps, [
++ (u"given", u"Given", u'scenario_1 step_1', None, None),
++ ])
++
++ # -- ENSURE: Disabling of background has no effect on other scenarios.
++ assert scenario2.name == "Scenario_2"
++ assert scenario2.background is feature.background
++ assert scenario2.background_steps == feature.background.steps
++ assert_compare_steps(scenario2.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'scenario_2 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_without_rule_background(self):
++ text = u'''
++ Feature: With Background and Rule
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Background and Rule"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is not None
++ # assert rule1_scenario1.background is not feature.background
++ assert rule1_scenario1.background_steps == feature.background.steps
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_inherit_feature_background_with_rule_background(self):
++ text = u'''
++ Feature: With Feature.Background and Rule.Background
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature.Background and Rule.Background"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'feature background step_1', None, None),
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_with_rule_background_when_background_inheritance_is_disabled(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++ assert rule1.background.inherited_steps != feature.background.steps
++ assert list(rule1.background.all_steps) != feature.background.steps
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_rule_background_when_background_inheritance_is_disabled_without(self):
++ # -- HINT: Background inheritance is enabled (by default).
++ text = u'''
++ Feature: With Feature Background Inheritance disabled
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ @fixture.behave.override_background
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "With Feature Background Inheritance disabled"
++ assert feature.background is not None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ rule1.use_background_inheritance = False # FIXTURE-EFFECT (simulated)
++ assert rule1.background is not None
++ assert rule1.background.use_inheritance is False
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_background_and_with_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and with Rule.Background
++
++ Rule: R1
++ Background: R1.Background
++ Given rule R1 background step_1
++
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and with Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is not None
++ assert rule1.background is not feature.background
++ assert rule1.background.inherited_steps == []
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == rule1.background.steps
++ assert rule1_scenario1.background_steps == list(rule1.background.all_steps)
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 background step_1', None, None),
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
++ ])
++
++ def test_parse__rule_scenarios_without_feature_and_rule_background(self):
++ text = u'''
++ Feature: Without Feature.Background and Rule.Background
++
++ Rule: R1
++ Scenario: R1.Scenario_1
++ Given rule R1 scenario_1 step_1
++ '''.lstrip()
++ feature = parse_feature(text)
++ assert feature.name == "Without Feature.Background and Rule.Background"
++ assert feature.background is None
++ assert len(feature.scenarios) == 0
++ assert len(feature.rules) == 1
++ assert len(feature.run_items) == 1
++
++ rule1 = feature.rules[0]
++ rule1_scenario1 = rule1.scenarios[0]
++ assert feature.run_items == [rule1]
++
++ assert rule1.background is None
++
++ assert rule1_scenario1.name == "R1.Scenario_1"
++ assert rule1_scenario1.background is None
++ assert rule1_scenario1.background is rule1.background
++ assert rule1_scenario1.background_steps == []
++ assert_compare_steps(rule1_scenario1.all_steps, [
++ (u"given", u"Given", u'rule R1 scenario_1 step_1', None, None),
+ ])
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
new file mode 100644
index 000000000..bbdce1e43
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
@@ -0,0 +1,269 @@
+From b6468eb1b71bd0877936882f57242b0cfa50c6ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:18:02 +0200
+Subject: [PATCH] Add support for runtime constraints.
+
+---
+ .bumpversion.cfg | 2 +-
+ behave/__init__.py | 2 +-
+ behave/__main__.py | 13 +++++----
+ behave/api/runtime_constraint.py | 45 ++++++++++++++++++++++++++++++++
+ behave/configuration.py | 4 ---
+ behave/exception.py | 40 ++++++++++++++++++++++++++++
+ behave/runner.py | 2 +-
+ behave/runner_util.py | 17 ++----------
+ behave/version.py | 2 ++
+ tests/unit/test_runner.py | 2 +-
+ 10 files changed, 101 insertions(+), 28 deletions(-)
+ create mode 100644 behave/api/runtime_constraint.py
+ create mode 100644 behave/exception.py
+ create mode 100644 behave/version.py
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index ac913c2..a5d3d2f 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,6 +1,6 @@
+ [bumpversion]
+ current_version = 1.2.7.dev1
+-files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
++files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+ commit = False
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 31e4e55..53a5337 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -20,6 +20,7 @@ from __future__ import absolute_import
+ from behave.step_registry import * # pylint: disable=wildcard-import
+ from behave.matchers import use_step_matcher, step_matcher, register_type
+ from behave.fixture import fixture, use_fixture
++from behave.version import VERSION as __version__
+
+ # pylint: disable=undefined-all-variable
+ __all__ = [
+@@ -29,4 +30,3 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev1"
+diff --git a/behave/__main__.py b/behave/__main__.py
+index c340b25..3cae36d 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -4,12 +4,13 @@ from __future__ import absolute_import, print_function
+ import codecs
+ import sys
+ import six
+-from behave import __version__
+-from behave.configuration import Configuration, ConfigError
++from behave.version import VERSION as BEHAVE_VERSION
++from behave.configuration import Configuration
++from behave.exception import ConstraintError, ConfigError, \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.parser import ParserError
+ from behave.runner import Runner
+-from behave.runner_util import print_undefined_step_snippets, reset_runtime, \
+- InvalidFileLocationError, InvalidFilenameError, FileNotFoundError
++from behave.runner_util import print_undefined_step_snippets, reset_runtime
+ from behave.textutil import compute_words_maxsize, text as _text
+
+
+@@ -62,7 +63,7 @@ def run_behave(config, runner_class=None):
+ runner_class = Runner
+
+ if config.version:
+- print("behave " + __version__)
++ print("behave " + BEHAVE_VERSION)
+ return 0
+
+ if config.tags_help:
+@@ -110,6 +111,8 @@ def run_behave(config, runner_class=None):
+ print(u"InvalidFileLocationError: %s" % e)
+ except InvalidFilenameError as e:
+ print(u"InvalidFilenameError: %s" % e)
++ except ConstraintError as e:
++ print(u"ConstraintError: %s" % e)
+ except Exception as e:
+ # -- DIAGNOSTICS:
+ text = _text(e)
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+new file mode 100644
+index 0000000..310e529
+--- /dev/null
++++ b/behave/api/runtime_constraint.py
+@@ -0,0 +1,45 @@
++# -*- coding: UTF-8 -*-
++"""
++Simplifies to specify runtime constraints in
++
++* features/environment.py file
++* features/steps/*.py" files
++"""
++
++from __future__ import absolute_import
++from behave.exception import ConstraintError
++
++
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def require_min_python_version(minimal_version):
++ """Simplifies to specify the minimal python version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ import six
++ import sys
++ python_version = sys.version_info
++ if isinstance(minimal_version, six.string_types):
++ python_version = "%s.%s" % sys.version_info[:2]
++ elif not isinstance(minimal_version, tuple):
++ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
++
++ if python_version < minimal_version:
++ raise ConstraintError("python >= %s expected (was: %s)" % \
++ (minimal_version, python_version))
++
++
++def require_min_behave_version(minimal_version):
++ """Simplifies to specify the minimal behave version that is required.
++
++ :param minimal_version: Minimum version (as string, tuple)
++ :raises: behave.exception.ConstraintError
++ """
++ # -- SIMPLISTIC IMPLEMENTATION:
++ from behave.version import VERSION as behave_version
++ if behave_version < minimal_version:
++ raise ConstraintError("behave >= %s expected (was: %s)" % \
++ (minimal_version, behave_version))
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 861f89f..bd8b039 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -62,10 +62,6 @@ class LogLevel(object):
+ return logging.getLevelName(level)
+
+
+-class ConfigError(Exception):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
+diff --git a/behave/exception.py b/behave/exception.py
+new file mode 100644
+index 0000000..ba21206
+--- /dev/null
++++ b/behave/exception.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++"""
++Behave exception classes.
++
++.. versionadded:: 1.2.7
++"""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES:
++# ---------------------------------------------------------------------------
++class ConstraintError(RuntimeError):
++ """Used if a constraint/precondition is not fulfilled at runtime.
++
++ .. versionadded:: 1.2.7
++ """
++
++
++class ConfigError(Exception):
++ """Used if the configuration is (partially) invalid."""
++
++
++# ---------------------------------------------------------------------------
++# EXCEPTION/ERROR CLASSES: Related to File Handling
++# ---------------------------------------------------------------------------
++class FileNotFoundError(LookupError):
++ """Used if a specified file was not found."""
++
++
++class InvalidFileLocationError(LookupError):
++ """Used if a :class:`behave.model_core.FileLocation` is invalid.
++ This occurs if the file location is no exactly correct and
++ strict checking is enabled.
++ """
++
++
++class InvalidFilenameError(ValueError):
++ """Used if a filename does not have the expected file extension, etc."""
++
++
+diff --git a/behave/runner.py b/behave/runner.py
+index f209cb0..cbedb5a 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -15,7 +15,7 @@ import six
+
+ from behave._types import ExceptionUtil
+ from behave.capture import CaptureController
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter._registry import make_formatters
+ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 7e0807f..80b99a0 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -11,26 +11,13 @@ import re
+ import sys
+ from six import string_types
+ from behave import parser
++from behave.exception import \
++ FileNotFoundError, InvalidFileLocationError, InvalidFilenameError
+ from behave.model_core import FileLocation
+ from behave.textutil import ensure_stream_with_encoder
+ # LAZY: from behave.step_registry import setup_step_decorators
+
+
+-# -----------------------------------------------------------------------------
+-# EXCEPTIONS:
+-# -----------------------------------------------------------------------------
+-class FileNotFoundError(LookupError):
+- pass
+-
+-
+-class InvalidFileLocationError(LookupError):
+- pass
+-
+-
+-class InvalidFilenameError(ValueError):
+- pass
+-
+-
+ # -----------------------------------------------------------------------------
+ # CLASS: FileLocationParser
+ # -----------------------------------------------------------------------------
+diff --git a/behave/version.py b/behave/version.py
+new file mode 100644
+index 0000000..b19cb5e
+--- /dev/null
++++ b/behave/version.py
+@@ -0,0 +1,2 @@
++# -- BEHAVE-VERSION:
++VERSION = "1.2.7.dev1"
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index 030dffa..f0d03cd 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,7 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
+-from behave.configuration import ConfigError
++from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
new file mode 100644
index 000000000..a5e627c9b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
@@ -0,0 +1,196 @@
+From 8f6bd98e566052d79b76f604ee792ffe456e859a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:19:13 +0200
+Subject: [PATCH] Use runtime constraints
+
+---
+ .../features/async_dispatch.feature | 2 ++
+ .../async_step/features/async_run.feature | 2 ++
+ examples/async_step/features/environment.py | 13 +++++++++
+ .../{async_steps34.py => _async_steps34.py} | 6 +++-
+ .../{async_steps35.py => _async_steps35.py} | 3 +-
+ .../features/steps/async_dispatch_steps.py | 29 +++++++++++++++----
+ .../async_step/features/steps/async_steps.py | 12 ++++++++
+ 7 files changed, 59 insertions(+), 8 deletions(-)
+ rename examples/async_step/features/steps/{async_steps34.py => _async_steps34.py} (58%)
+ rename examples/async_step/features/steps/{async_steps35.py => _async_steps35.py} (95%)
+ create mode 100644 examples/async_step/features/steps/async_steps.py
+
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 416d3d1..18e9869 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 9f506b4..29b8fa7 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,6 +1,8 @@
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++@use.with_python.version=3.7
++@use.with_python.version=3.8
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 9d4302b..02c4d92 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -1,8 +1,18 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.api.runtime_constraint import require_min_python_version
+ import sys
+
++# -----------------------------------------------------------------------------
++# REQUIRE: python >= 3.4
++# -----------------------------------------------------------------------------
++require_min_python_version("3.4")
++
++
++# -----------------------------------------------------------------------------
++# SUPPORT: Active-tags
++# -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+ python_version = "%s.%s" % sys.version_info[:2]
+@@ -11,6 +21,7 @@ active_tag_value_provider = {
+ }
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +29,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/examples/async_step/features/steps/async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+similarity index 58%
+rename from examples/async_step/features/steps/async_steps34.py
+rename to examples/async_step/features/steps/_async_steps34.py
+index c4962ab..556500f 100644
+--- a/examples/async_step/features/steps/async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,8 +1,12 @@
+-# -- REQUIRES: Python >= 3.4
++# -- REQUIRES: Python >= 3.4 and Python < 3.8
++# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# USE: Async generator/coroutine instead.
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+
++# -- USABLE FOR: "3.4" <= python_version < "3.8"
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ @asyncio.coroutine
+diff --git a/examples/async_step/features/steps/async_steps35.py b/examples/async_step/features/steps/_async_steps35.py
+similarity index 95%
+rename from examples/async_step/features/steps/async_steps35.py
+rename to examples/async_step/features/steps/_async_steps35.py
+index 018d5ef..edcbe0e 100644
+--- a/examples/async_step/features/steps/async_steps35.py
++++ b/examples/async_step/features/steps/_async_steps35.py
+@@ -1,4 +1,5 @@
+ # -- REQUIRES: Python >= 3.5
++
+ from behave import step
+ from behave.api.async_step import async_run_until_complete
+ import asyncio
+@@ -6,5 +7,5 @@ import asyncio
+ @step('an async-step waits {duration:f} seconds')
+ @async_run_until_complete
+ async def step_async_step_waits_seconds_py35(context, duration):
+- """Simple example of a coroutine as async-step (in Python 3.5)"""
++ """Simple example of a coroutine as async-step (in Python 3.5 or newer)"""
+ await asyncio.sleep(duration)
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index b9b6e15..222e54d 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,21 +1,38 @@
+ # -*- coding: UTF-8 -*-
+-# REQUIRES: Python >= 3.5
++# REQUIRES: Python >= 3.4/3.5
++import sys
+ from behave import given, then, step
+-from behave.api.async_step import use_or_create_async_context, AsyncContext
++from behave.api.async_step import use_or_create_async_context
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+-@asyncio.coroutine
+-def async_func(param):
+- yield from asyncio.sleep(0.2)
+- return str(param).upper()
+
++# ---------------------------------------------------------------------------
++# ASYNC EXAMPLE FUNCTION:
++# ---------------------------------------------------------------------------
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ async def async_func(param):
++ await asyncio.sleep(0.2)
++ return str(param).upper()
++else:
++ # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++ @asyncio.coroutine
++ def async_func(param):
++ yield from asyncio.sleep(0.2)
++ return str(param).upper()
++
++
++# ---------------------------------------------------------------------------
++# STEPS:
++# ---------------------------------------------------------------------------
+ @given('I dispatch an async-call with param "{param}"')
+ def step_dispatch_async_call(context, param):
+ async_context = use_or_create_async_context(context, "async_context1")
+ task = async_context.loop.create_task(async_func(param))
+ async_context.tasks.append(task)
+
++
+ @then('the collected result of the async-calls is "{expected}"')
+ def step_collected_async_call_result_is(context, expected):
+ async_context = context.async_context1
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+new file mode 100644
+index 0000000..dc03c72
+--- /dev/null
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++# REQUIRES: Python >= 3.4/3.5
++"""Python import-barrier for python2 or python < 3.4."""
++
++from __future__ import absolute_import
++import sys
++
++python_version = "%s.%s" % sys.version_info[:2]
++if python_version >= "3.5":
++ import _async_steps35
++elif python_version == "3.4":
++ import _async_steps34
diff --git a/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..e314d9118
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,3937 @@
+From 393d90b6127b61def5d9d12444c2a747a03f7ec1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:20:38 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ .attic/convert_i18n_yaml.py | 77 +
+ .attic/i18n.yml | 635 +++++++
+ bin/gherkin-languages.json | 3193 -----------------------------------
+ 3 files changed, 712 insertions(+), 3193 deletions(-)
+ create mode 100755 .attic/convert_i18n_yaml.py
+ create mode 100644 .attic/i18n.yml
+ delete mode 100644 bin/gherkin-languages.json
+
+diff --git a/.attic/convert_i18n_yaml.py b/.attic/convert_i18n_yaml.py
+new file mode 100755
+index 0000000..d6a6713
+--- /dev/null
++++ b/.attic/convert_i18n_yaml.py
+@@ -0,0 +1,77 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
++"""
++Generates I18N python module based on YAML description (i18n.yml).
++
++REQUIRES:
++ * argparse
++ * six
++ * PyYAML
++"""
++
++from __future__ import absolute_import, print_function
++import argparse
++import os.path
++import six
++import sys
++import pprint
++import yaml
++
++HERE = os.path.dirname(__file__)
++NAME = os.path.basename(__file__)
++__version__ = "1.0"
++
++def yaml_normalize(data):
++ for part in data:
++ keywords = data[part]
++ for k in keywords:
++ v = keywords[k]
++ # bloody YAML parser returns a mixture of unicode and str
++ if not isinstance(v, six.text_type):
++ v = v.decode("UTF-8")
++ keywords[k] = v.split("|")
++ return data
++
++def main(args=None):
++ if args is None:
++ args = sys.argv[1:]
++ parser = argparse.ArgumentParser(prog=NAME,
++ description="Generate python module i18n from YAML based data")
++ parser.add_argument("-d", "--data", dest="yaml_file",
++ default=os.path.join(HERE, "i18n.yml"),
++ help="Path to i18n.yml file (YAML file).")
++ parser.add_argument("output_file", default="stdout",
++ help="Filename of Python I18N module (as output).")
++ parser.add_argument("--version", action="version", version=__version__)
++
++ options = parser.parse_args(args)
++ if not os.path.isfile(options.yaml_file):
++ parser.error("YAML file not found: %s" % options.yaml_file)
++
++ # -- STEP 1: Load YAML data.
++ languages = yaml.load(open(options.yaml_file))
++ languages = yaml_normalize(languages)
++
++ # -- STEP 2: Generate python module with i18n data.
++ contents = u"""# -*- coding: UTF-8 -*-
++# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
++# pylint: disable=line-too-long
++
++languages = \\
++"""
++ if options.output_file in ("-", "stdout"):
++ i18n_py = sys.stdout
++ should_close = False
++ else:
++ i18n_py = open(options.output_file, "w")
++ should_close = True
++ i18n_py.write(contents.encode("UTF-8"))
++ i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
++ i18n_py.write(u"\n")
++ if should_close:
++ i18n_py.close()
++ return 0
++
++if __name__ == "__main__":
++ sys.exit(main())
+diff --git a/.attic/i18n.yml b/.attic/i18n.yml
+new file mode 100644
+index 0000000..82345a4
+--- /dev/null
++++ b/.attic/i18n.yml
+@@ -0,0 +1,635 @@
++# encoding: UTF-8
++#
++# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
++# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
++# http://en.wikipedia.org/wiki/ISO_3166-1
++#
++# If you want several aliases for a keyword, just separate them
++# with a | character. The * is a step keyword alias for all translations.
++#
++# If you do *not* want a trailing space after a keyword, end it with a < character.
++# (See Chinese for examples).
++#
++# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
++#
++# 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.
++
++"en":
++ name: English
++ native: English
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: Scenario Outline|Scenario Template
++ examples: Examples|Scenarios
++ given: "*|Given"
++ when: "*|When"
++ then: "*|Then"
++ and: "*|And"
++ but: "*|But"
++
++# Please keep the grammars in alphabetical order by name from here and down.
++
++"ar":
++ name: Arabic
++ native: العربية
++ feature: خاصية
++ background: الخلفية
++ scenario: سيناريو
++ scenario_outline: سيناريو مخطط
++ examples: امثلة
++ given: "*|بفرض"
++ when: "*|متى|عندما"
++ then: "*|اذاً|ثم"
++ and: "*|و"
++ but: "*|لكن"
++"bg":
++ name: Bulgarian
++ native: български
++ feature: Функционалност
++ background: Предистория
++ scenario: Сценарий
++ scenario_outline: Рамка на сценарий
++ examples: Примери
++ given: "*|Дадено"
++ when: "*|Когато"
++ then: "*|То"
++ and: "*|И"
++ but: "*|Но"
++"ca":
++ name: Catalan
++ native: català
++ background: Rerefons|Antecedents
++ feature: Característica|Funcionalitat
++ scenario: Escenari
++ scenario_outline: Esquema de l'escenari
++ examples: Exemples
++ given: "*|Donat|Donada|Atès|Atesa"
++ when: "*|Quan"
++ then: "*|Aleshores|Cal"
++ and: "*|I"
++ but: "*|Però"
++"cy-GB":
++ name: Welsh
++ native: Cymraeg
++ background: Cefndir
++ feature: Arwedd
++ scenario: Scenario
++ scenario_outline: Scenario Amlinellol
++ examples: Enghreifftiau
++ given: "*|Anrhegedig a"
++ when: "*|Pryd"
++ then: "*|Yna"
++ and: "*|A"
++ but: "*|Ond"
++"cs":
++ name: Czech
++ native: Česky
++ feature: Požadavek
++ background: Pozadí|Kontext
++ scenario: Scénář
++ scenario_outline: Náčrt Scénáře|Osnova scénáře
++ examples: Příklady
++ given: "*|Pokud|Za předpokladu"
++ when: "*|Když"
++ then: "*|Pak"
++ and: "*|A|A také"
++ but: "*|Ale"
++"da":
++ name: Danish
++ native: dansk
++ feature: Egenskab
++ background: Baggrund
++ scenario: Scenarie
++ scenario_outline: Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Givet"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"de":
++ name: German
++ native: Deutsch
++ feature: Funktionalität
++ background: Grundlage
++ scenario: Szenario
++ scenario_outline: Szenariogrundriss
++ examples: Beispiele
++ given: "*|Angenommen|Gegeben sei"
++ when: "*|Wenn"
++ then: "*|Dann"
++ and: "*|Und"
++ but: "*|Aber"
++"en-au":
++ name: Australian
++ native: Australian
++ feature: Crikey
++ background: Background
++ scenario: Mate
++ scenario_outline: Blokes
++ examples: Cobber
++ given: "*|Ya know how"
++ when: "*|When"
++ then: "*|Ya gotta"
++ and: "*|N"
++ but: "*|Cept"
++"en-lol":
++ name: LOLCAT
++ native: LOLCAT
++ feature: OH HAI
++ background: B4
++ scenario: MISHUN
++ scenario_outline: MISHUN SRSLY
++ examples: EXAMPLZ
++ given: "*|I CAN HAZ"
++ when: "*|WEN"
++ then: "*|DEN"
++ and: "*|AN"
++ but: "*|BUT"
++"en-pirate":
++ name: Pirate
++ native: Pirate
++ feature: Ahoy matey!
++ background: Yo-ho-ho
++ scenario: Heave to
++ scenario_outline: Shiver me timbers
++ examples: Dead men tell no tales
++ given: "*|Gangway!"
++ when: "*|Blimey!"
++ then: "*|Let go and haul"
++ and: "*|Aye"
++ but: "*|Avast!"
++"en-Scouse":
++ name: Scouse
++ native: Scouse
++ feature: Feature
++ background: "Dis is what went down"
++ scenario: "The thing of it is"
++ scenario_outline: "Wharrimean is"
++ examples: Examples
++ given: "*|Givun|Youse know when youse got"
++ when: "*|Wun|Youse know like when"
++ then: "*|Dun|Den youse gotta"
++ and: "*|An"
++ but: "*|Buh"
++"en-tx":
++ name: Texan
++ native: Texan
++ feature: Feature
++ background: Background
++ scenario: Scenario
++ scenario_outline: All y'all
++ examples: Examples
++ given: "*|Given y'all"
++ when: "*|When y'all"
++ then: "*|Then y'all"
++ and: "*|And y'all"
++ but: "*|But y'all"
++"eo":
++ name: Esperanto
++ native: Esperanto
++ feature: Trajto
++ background: Fono
++ scenario: Scenaro
++ scenario_outline: Konturo de la scenaro
++ examples: Ekzemploj
++ given: "*|Donitaĵo"
++ when: "*|Se"
++ then: "*|Do"
++ and: "*|Kaj"
++ but: "*|Sed"
++"es":
++ name: Spanish
++ native: español
++ background: Antecedentes
++ feature: Característica
++ scenario: Escenario
++ scenario_outline: Esquema del escenario
++ examples: Ejemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cuando"
++ then: "*|Entonces"
++ and: "*|Y"
++ but: "*|Pero"
++"et":
++ name: Estonian
++ native: eesti keel
++ feature: Omadus
++ background: Taust
++ scenario: Stsenaarium
++ scenario_outline: Raamstsenaarium
++ examples: Juhtumid
++ given: "*|Eeldades"
++ when: "*|Kui"
++ then: "*|Siis"
++ and: "*|Ja"
++ but: "*|Kuid"
++"fi":
++ name: Finnish
++ native: suomi
++ feature: Ominaisuus
++ background: Tausta
++ scenario: Tapaus
++ scenario_outline: Tapausaihio
++ examples: Tapaukset
++ given: "*|Oletetaan"
++ when: "*|Kun"
++ then: "*|Niin"
++ and: "*|Ja"
++ but: "*|Mutta"
++"fr":
++ name: French
++ native: français
++ feature: Fonctionnalité
++ background: Contexte
++ scenario: Scénario
++ scenario_outline: Plan du scénario|Plan du Scénario
++ examples: Exemples
++ given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
++ when: "*|Quand|Lorsque|Lorsqu'<"
++ then: "*|Alors"
++ and: "*|Et"
++ but: "*|Mais"
++"gl":
++ name: Galician
++ native: galego
++ feature: Característica
++ background: Contexto
++ scenario: Escenario
++ scenario_outline: "Esbozo do escenario"
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Cando"
++ then: "*|Entón|Logo"
++ and: "*|E"
++ but: "*|Mais|Pero"
++
++"he":
++ name: Hebrew
++ native: עברית
++ feature: תכונה
++ background: רקע
++ scenario: תרחיש
++ scenario_outline: תבנית תרחיש
++ examples: דוגמאות
++ given: "*|בהינתן"
++ when: "*|כאשר"
++ then: "*|אז|אזי"
++ and: "*|וגם"
++ but: "*|אבל"
++"hr":
++ name: Croatian
++ native: hrvatski
++ feature: Osobina|Mogućnost|Mogucnost
++ background: Pozadina
++ scenario: Scenarij
++ scenario_outline: Skica|Koncept
++ examples: Primjeri|Scenariji
++ given: "*|Zadan|Zadani|Zadano"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"hu":
++ name: Hungarian
++ native: magyar
++ feature: Jellemző
++ background: Háttér
++ scenario: Forgatókönyv
++ scenario_outline: Forgatókönyv vázlat
++ examples: Példák
++ given: "*|Amennyiben|Adott"
++ when: "*|Majd|Ha|Amikor"
++ then: "*|Akkor"
++ and: "*|És"
++ but: "*|De"
++"id":
++ name: Indonesian
++ native: Bahasa Indonesia
++ feature: Fitur
++ background: Dasar
++ scenario: Skenario
++ scenario_outline: Skenario konsep
++ examples: Contoh
++ given: "*|Dengan"
++ when: "*|Ketika"
++ then: "*|Maka"
++ and: "*|Dan"
++ but: "*|Tapi"
++"is":
++ name: Icelandic
++ native: Íslenska
++ feature: Eiginleiki
++ background: Bakgrunnur
++ scenario: Atburðarás
++ scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
++ examples: Dæmi|Atburðarásir
++ given: "*|Ef"
++ when: "*|Þegar"
++ then: "*|Þá"
++ and: "*|Og"
++ but: "*|En"
++"it":
++ name: Italian
++ native: italiano
++ feature: Funzionalità
++ background: Contesto
++ scenario: Scenario
++ scenario_outline: Schema dello scenario
++ examples: Esempi
++ given: "*|Dato|Data|Dati|Date"
++ when: "*|Quando"
++ then: "*|Allora"
++ and: "*|E"
++ but: "*|Ma"
++"ja":
++ name: Japanese
++ native: 日本語
++ feature: フィーチャ|機能
++ background: 背景
++ scenario: シナリオ
++ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
++ examples: 例|サンプル
++ given: "*|前提<"
++ when: "*|もし<"
++ then: "*|ならば<"
++ and: "*|かつ<"
++ but: "*|しかし<|但し<|ただし<"
++"ko":
++ name: Korean
++ native: 한국어
++ background: 배경
++ feature: 기능
++ scenario: 시나리오
++ scenario_outline: 시나리오 개요
++ examples: 예
++ given: "*|조건<|먼저<"
++ when: "*|만일<|만약<"
++ then: "*|그러면<"
++ and: "*|그리고<"
++ but: "*|하지만<|단<"
++"lt":
++ name: Lithuanian
++ native: lietuvių kalba
++ feature: Savybė
++ background: Kontekstas
++ scenario: Scenarijus
++ scenario_outline: Scenarijaus šablonas
++ examples: Pavyzdžiai|Scenarijai|Variantai
++ given: "*|Duota"
++ when: "*|Kai"
++ then: "*|Tada"
++ and: "*|Ir"
++ but: "*|Bet"
++"lu":
++ name: Luxemburgish
++ native: Lëtzebuergesch
++ feature: Funktionalitéit
++ background: Hannergrond
++ scenario: Szenario
++ scenario_outline: Plang vum Szenario
++ examples: Beispiller
++ given: "*|ugeholl"
++ when: "*|wann"
++ then: "*|dann"
++ and: "*|an|a"
++ but: "*|awer|mä"
++"lv":
++ name: Latvian
++ native: latviešu
++ feature: Funkcionalitāte|Fīča
++ background: Konteksts|Situācija
++ scenario: Scenārijs
++ scenario_outline: Scenārijs pēc parauga
++ examples: Piemēri|Paraugs
++ given: "*|Kad"
++ when: "*|Ja"
++ then: "*|Tad"
++ and: "*|Un"
++ but: "*|Bet"
++"nl":
++ name: Dutch
++ native: Nederlands
++ feature: Functionaliteit
++ background: Achtergrond
++ scenario: Scenario
++ scenario_outline: Abstract Scenario
++ examples: Voorbeelden
++ given: "*|Gegeven|Stel"
++ when: "*|Als"
++ then: "*|Dan"
++ and: "*|En"
++ but: "*|Maar"
++"no":
++ name: Norwegian
++ native: norsk
++ feature: Egenskap
++ background: Bakgrunn
++ scenario: Scenario
++ scenario_outline: Scenariomal|Abstrakt Scenario
++ examples: Eksempler
++ given: "*|Gitt"
++ when: "*|Når"
++ then: "*|Så"
++ and: "*|Og"
++ but: "*|Men"
++"pl":
++ name: Polish
++ native: polski
++ feature: Właściwość
++ background: Założenia
++ scenario: Scenariusz
++ scenario_outline: Szablon scenariusza
++ examples: Przykłady
++ given: "*|Zakładając|Mając"
++ when: "*|Jeżeli|Jeśli"
++ then: "*|Wtedy"
++ and: "*|Oraz|I"
++ but: "*|Ale"
++"pt":
++ name: Portuguese
++ native: português
++ background: Contexto
++ feature: Funcionalidade
++ scenario: Cenário|Cenario
++ scenario_outline: Esquema do Cenário|Esquema do Cenario
++ examples: Exemplos
++ given: "*|Dado|Dada|Dados|Dadas"
++ when: "*|Quando"
++ then: "*|Então|Entao"
++ and: "*|E"
++ but: "*|Mas"
++"ro":
++ name: Romanian
++ native: română
++ background: Context
++ feature: Functionalitate|Funcționalitate|Funcţionalitate
++ scenario: Scenariu
++ scenario_outline: Structura scenariu|Structură scenariu
++ examples: Exemple
++ given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
++ when: "*|Cand|Când"
++ then: "*|Atunci"
++ and: "*|Si|Și|Şi"
++ but: "*|Dar"
++"ru":
++ name: Russian
++ native: русский
++ feature: Функция|Функционал|Свойство
++ background: Предыстория|Контекст
++ scenario: Сценарий
++ scenario_outline: Структура сценария
++ examples: Примеры
++ given: "*|Допустим|Дано|Пусть"
++ when: "*|Если|Когда"
++ then: "*|То|Тогда"
++ and: "*|И|К тому же"
++ but: "*|Но|А"
++"sv":
++ name: Swedish
++ native: Svenska
++ feature: Egenskap
++ background: Bakgrund
++ scenario: Scenario
++ scenario_outline: Abstrakt Scenario|Scenariomall
++ examples: Exempel
++ given: "*|Givet"
++ when: "*|När"
++ then: "*|Så"
++ and: "*|Och"
++ but: "*|Men"
++"sk":
++ name: Slovak
++ native: Slovensky
++ feature: Požiadavka
++ background: Pozadie
++ scenario: Scenár
++ scenario_outline: Náčrt Scenáru
++ examples: Príklady
++ given: "*|Pokiaľ"
++ when: "*|Keď"
++ then: "*|Tak"
++ and: "*|A"
++ but: "*|Ale"
++"sr-Latn":
++ name: Serbian (Latin)
++ native: Srpski (Latinica)
++ feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
++ background: Kontekst|Osnova|Pozadina
++ scenario: Scenario|Primer
++ scenario_outline: Struktura scenarija|Skica|Koncept
++ examples: Primeri|Scenariji
++ given: "*|Zadato|Zadate|Zatati"
++ when: "*|Kada|Kad"
++ then: "*|Onda"
++ and: "*|I"
++ but: "*|Ali"
++"sr-Cyrl":
++ name: Serbian
++ native: Српски
++ feature: Функционалност|Могућност|Особина
++ background: Контекст|Основа|Позадина
++ scenario: Сценарио|Пример
++ scenario_outline: Структура сценарија|Скица|Концепт
++ examples: Примери|Сценарији
++ given: "*|Задато|Задате|Задати"
++ when: "*|Када|Кад"
++ then: "*|Онда"
++ and: "*|И"
++ but: "*|Али"
++"tr":
++ name: Turkish
++ native: Türkçe
++ feature: Özellik
++ background: Geçmiş
++ scenario: Senaryo
++ scenario_outline: Senaryo taslağı
++ examples: Örnekler
++ given: "*|Diyelim ki"
++ when: "*|Eğer ki"
++ then: "*|O zaman"
++ and: "*|Ve"
++ but: "*|Fakat|Ama"
++"uk":
++ name: Ukrainian
++ native: Українська
++ feature: Функціонал
++ background: Передумова
++ scenario: Сценарій
++ scenario_outline: Структура сценарію
++ examples: Приклади
++ given: "*|Припустимо|Припустимо, що|Нехай|Дано"
++ when: "*|Якщо|Коли"
++ then: "*|То|Тоді"
++ and: "*|І|А також|Та"
++ but: "*|Але"
++"uz":
++ name: Uzbek
++ native: Узбекча
++ feature: Функционал
++ background: Тарих
++ scenario: Сценарий
++ scenario_outline: Сценарий структураси
++ examples: Мисоллар
++ given: "*|Агар"
++ when: "*|Агар"
++ then: "*|Унда"
++ and: "*|Ва"
++ but: "*|Лекин|Бирок|Аммо"
++"vi":
++ name: Vietnamese
++ native: Tiếng Việt
++ feature: Tính năng
++ background: Bối cảnh
++ scenario: Tình huống|Kịch bản
++ scenario_outline: Khung tình huống|Khung kịch bản
++ examples: Dữ liệu
++ given: "*|Biết|Cho"
++ when: "*|Khi"
++ then: "*|Thì"
++ and: "*|Và"
++ but: "*|Nhưng"
++"zh-CN":
++ name: Chinese simplified
++ native: 简体中文
++ feature: 功能
++ background: 背景
++ scenario: 场景
++ scenario_outline: 场景大纲
++ examples: 例子
++ given: "*|假如<"
++ when: "*|当<"
++ then: "*|那么<"
++ and: "*|而且<"
++ but: "*|但是<"
++"zh-TW":
++ name: Chinese traditional
++ native: 繁體中文
++ feature: 功能
++ background: 背景
++ scenario: 場景|劇本
++ scenario_outline: 場景大綱|劇本大綱
++ examples: 例子
++ given: "*|假設<"
++ when: "*|當<"
++ then: "*|那麼<"
++ and: "*|而且<|並且<"
++ but: "*|但是<"
+diff --git a/bin/gherkin-languages.json b/bin/gherkin-languages.json
+deleted file mode 100644
+index d2d5e93..0000000
+--- a/bin/gherkin-languages.json
++++ /dev/null
+@@ -1,3193 +0,0 @@
+-{
+- "af": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Agtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelde"
+- ],
+- "feature": [
+- "Funksie",
+- "Besigheid Behoefte",
+- "Vermoë"
+- ],
+- "given": [
+- "* ",
+- "Gegewe "
+- ],
+- "name": "Afrikaans",
+- "native": "Afrikaans",
+- "scenario": [
+- "Situasie"
+- ],
+- "scenarioOutline": [
+- "Situasie Uiteensetting"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Wanneer "
+- ]
+- },
+- "am": {
+- "and": [
+- "* ",
+- "Եվ "
+- ],
+- "background": [
+- "Կոնտեքստ"
+- ],
+- "but": [
+- "* ",
+- "Բայց "
+- ],
+- "examples": [
+- "Օրինակներ"
+- ],
+- "feature": [
+- "Ֆունկցիոնալություն",
+- "Հատկություն"
+- ],
+- "given": [
+- "* ",
+- "Դիցուք "
+- ],
+- "name": "Armenian",
+- "native": "հայերեն",
+- "scenario": [
+- "Սցենար"
+- ],
+- "scenarioOutline": [
+- "Սցենարի կառուցվացքը"
+- ],
+- "then": [
+- "* ",
+- "Ապա "
+- ],
+- "when": [
+- "* ",
+- "Եթե ",
+- "Երբ "
+- ]
+- },
+- "ar": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "الخلفية"
+- ],
+- "but": [
+- "* ",
+- "لكن "
+- ],
+- "examples": [
+- "امثلة"
+- ],
+- "feature": [
+- "خاصية"
+- ],
+- "given": [
+- "* ",
+- "بفرض "
+- ],
+- "name": "Arabic",
+- "native": "العربية",
+- "scenario": [
+- "سيناريو"
+- ],
+- "scenarioOutline": [
+- "سيناريو مخطط"
+- ],
+- "then": [
+- "* ",
+- "اذاً ",
+- "ثم "
+- ],
+- "when": [
+- "* ",
+- "متى ",
+- "عندما "
+- ]
+- },
+- "ast": {
+- "and": [
+- "* ",
+- "Y ",
+- "Ya "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Peru "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Carauterística"
+- ],
+- "given": [
+- "* ",
+- "Dáu ",
+- "Dada ",
+- "Daos ",
+- "Daes "
+- ],
+- "name": "Asturian",
+- "native": "asturianu",
+- "scenario": [
+- "Casu"
+- ],
+- "scenarioOutline": [
+- "Esbozu del casu"
+- ],
+- "then": [
+- "* ",
+- "Entós "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "az": {
+- "and": [
+- "* ",
+- "Və ",
+- "Həm "
+- ],
+- "background": [
+- "Keçmiş",
+- "Kontekst"
+- ],
+- "but": [
+- "* ",
+- "Amma ",
+- "Ancaq "
+- ],
+- "examples": [
+- "Nümunələr"
+- ],
+- "feature": [
+- "Özəllik"
+- ],
+- "given": [
+- "* ",
+- "Tutaq ki ",
+- "Verilir "
+- ],
+- "name": "Azerbaijani",
+- "native": "Azərbaycanca",
+- "scenario": [
+- "Ssenari"
+- ],
+- "scenarioOutline": [
+- "Ssenarinin strukturu"
+- ],
+- "then": [
+- "* ",
+- "O halda "
+- ],
+- "when": [
+- "* ",
+- "Əgər ",
+- "Nə vaxt ki "
+- ]
+- },
+- "bg": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Предистория"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери"
+- ],
+- "feature": [
+- "Функционалност"
+- ],
+- "given": [
+- "* ",
+- "Дадено "
+- ],
+- "name": "Bulgarian",
+- "native": "български",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Рамка на сценарий"
+- ],
+- "then": [
+- "* ",
+- "То "
+- ],
+- "when": [
+- "* ",
+- "Когато "
+- ]
+- },
+- "bm": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Latar Belakang"
+- ],
+- "but": [
+- "* ",
+- "Tetapi ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fungsi"
+- ],
+- "given": [
+- "* ",
+- "Diberi ",
+- "Bagi "
+- ],
+- "name": "Malay",
+- "native": "Bahasa Melayu",
+- "scenario": [
+- "Senario",
+- "Situasi",
+- "Keadaan"
+- ],
+- "scenarioOutline": [
+- "Kerangka Senario",
+- "Kerangka Situasi",
+- "Kerangka Keadaan",
+- "Garis Panduan Senario"
+- ],
+- "then": [
+- "* ",
+- "Maka ",
+- "Kemudian "
+- ],
+- "when": [
+- "* ",
+- "Apabila "
+- ]
+- },
+- "bs": {
+- "and": [
+- "* ",
+- "I ",
+- "A "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri"
+- ],
+- "feature": [
+- "Karakteristika"
+- ],
+- "given": [
+- "* ",
+- "Dato "
+- ],
+- "name": "Bosnian",
+- "native": "Bosanski",
+- "scenario": [
+- "Scenariju",
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariju-obris",
+- "Scenario-outline"
+- ],
+- "then": [
+- "* ",
+- "Zatim "
+- ],
+- "when": [
+- "* ",
+- "Kada "
+- ]
+- },
+- "ca": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Rerefons",
+- "Antecedents"
+- ],
+- "but": [
+- "* ",
+- "Però "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Característica",
+- "Funcionalitat"
+- ],
+- "given": [
+- "* ",
+- "Donat ",
+- "Donada ",
+- "Atès ",
+- "Atesa "
+- ],
+- "name": "Catalan",
+- "native": "català",
+- "scenario": [
+- "Escenari"
+- ],
+- "scenarioOutline": [
+- "Esquema de l'escenari"
+- ],
+- "then": [
+- "* ",
+- "Aleshores ",
+- "Cal "
+- ],
+- "when": [
+- "* ",
+- "Quan "
+- ]
+- },
+- "cs": {
+- "and": [
+- "* ",
+- "A také ",
+- "A "
+- ],
+- "background": [
+- "Pozadí",
+- "Kontext"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Příklady"
+- ],
+- "feature": [
+- "Požadavek"
+- ],
+- "given": [
+- "* ",
+- "Pokud ",
+- "Za předpokladu "
+- ],
+- "name": "Czech",
+- "native": "Česky",
+- "scenario": [
+- "Scénář"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scénáře",
+- "Osnova scénáře"
+- ],
+- "then": [
+- "* ",
+- "Pak "
+- ],
+- "when": [
+- "* ",
+- "Když "
+- ]
+- },
+- "cy-GB": {
+- "and": [
+- "* ",
+- "A "
+- ],
+- "background": [
+- "Cefndir"
+- ],
+- "but": [
+- "* ",
+- "Ond "
+- ],
+- "examples": [
+- "Enghreifftiau"
+- ],
+- "feature": [
+- "Arwedd"
+- ],
+- "given": [
+- "* ",
+- "Anrhegedig a "
+- ],
+- "name": "Welsh",
+- "native": "Cymraeg",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Amlinellol"
+- ],
+- "then": [
+- "* ",
+- "Yna "
+- ],
+- "when": [
+- "* ",
+- "Pryd "
+- ]
+- },
+- "da": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Baggrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskab"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Danish",
+- "native": "dansk",
+- "scenario": [
+- "Scenarie"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "de": {
+- "and": [
+- "* ",
+- "Und "
+- ],
+- "background": [
+- "Grundlage"
+- ],
+- "but": [
+- "* ",
+- "Aber "
+- ],
+- "examples": [
+- "Beispiele"
+- ],
+- "feature": [
+- "Funktionalität"
+- ],
+- "given": [
+- "* ",
+- "Angenommen ",
+- "Gegeben sei ",
+- "Gegeben seien "
+- ],
+- "name": "German",
+- "native": "Deutsch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Szenariogrundriss"
+- ],
+- "then": [
+- "* ",
+- "Dann "
+- ],
+- "when": [
+- "* ",
+- "Wenn "
+- ]
+- },
+- "el": {
+- "and": [
+- "* ",
+- "Και "
+- ],
+- "background": [
+- "Υπόβαθρο"
+- ],
+- "but": [
+- "* ",
+- "Αλλά "
+- ],
+- "examples": [
+- "Παραδείγματα",
+- "Σενάρια"
+- ],
+- "feature": [
+- "Δυνατότητα",
+- "Λειτουργία"
+- ],
+- "given": [
+- "* ",
+- "Δεδομένου "
+- ],
+- "name": "Greek",
+- "native": "Ελληνικά",
+- "scenario": [
+- "Σενάριο"
+- ],
+- "scenarioOutline": [
+- "Περιγραφή Σεναρίου",
+- "Περίγραμμα Σεναρίου"
+- ],
+- "then": [
+- "* ",
+- "Τότε "
+- ],
+- "when": [
+- "* ",
+- "Όταν "
+- ]
+- },
+- "em": {
+- "and": [
+- "* ",
+- "😂"
+- ],
+- "background": [
+- "💤"
+- ],
+- "but": [
+- "* ",
+- "😔"
+- ],
+- "examples": [
+- "📓"
+- ],
+- "feature": [
+- "📚"
+- ],
+- "given": [
+- "* ",
+- "😐"
+- ],
+- "name": "Emoji",
+- "native": "😀",
+- "scenario": [
+- "📕"
+- ],
+- "scenarioOutline": [
+- "📖"
+- ],
+- "then": [
+- "* ",
+- "🙏"
+- ],
+- "when": [
+- "* ",
+- "🎬"
+- ]
+- },
+- "en": {
+- "and": [
+- "* ",
+- "And "
+- ],
+- "background": [
+- "Background"
+- ],
+- "but": [
+- "* ",
+- "But "
+- ],
+- "examples": [
+- "Examples",
+- "Scenarios"
+- ],
+- "feature": [
+- "Feature",
+- "Business Need",
+- "Ability"
+- ],
+- "given": [
+- "* ",
+- "Given "
+- ],
+- "name": "English",
+- "native": "English",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenario Outline",
+- "Scenario Template"
+- ],
+- "then": [
+- "* ",
+- "Then "
+- ],
+- "when": [
+- "* ",
+- "When "
+- ]
+- },
+- "en-Scouse": {
+- "and": [
+- "* ",
+- "An "
+- ],
+- "background": [
+- "Dis is what went down"
+- ],
+- "but": [
+- "* ",
+- "Buh "
+- ],
+- "examples": [
+- "Examples"
+- ],
+- "feature": [
+- "Feature"
+- ],
+- "given": [
+- "* ",
+- "Givun ",
+- "Youse know when youse got "
+- ],
+- "name": "Scouse",
+- "native": "Scouse",
+- "scenario": [
+- "The thing of it is"
+- ],
+- "scenarioOutline": [
+- "Wharrimean is"
+- ],
+- "then": [
+- "* ",
+- "Dun ",
+- "Den youse gotta "
+- ],
+- "when": [
+- "* ",
+- "Wun ",
+- "Youse know like when "
+- ]
+- },
+- "en-au": {
+- "and": [
+- "* ",
+- "Too right "
+- ],
+- "background": [
+- "First off"
+- ],
+- "but": [
+- "* ",
+- "Yeah nah "
+- ],
+- "examples": [
+- "You'll wanna"
+- ],
+- "feature": [
+- "Pretty much"
+- ],
+- "given": [
+- "* ",
+- "Y'know "
+- ],
+- "name": "Australian",
+- "native": "Australian",
+- "scenario": [
+- "Awww, look mate"
+- ],
+- "scenarioOutline": [
+- "Reckon it's like"
+- ],
+- "then": [
+- "* ",
+- "But at the end of the day I reckon "
+- ],
+- "when": [
+- "* ",
+- "It's just unbelievable "
+- ]
+- },
+- "en-lol": {
+- "and": [
+- "* ",
+- "AN "
+- ],
+- "background": [
+- "B4"
+- ],
+- "but": [
+- "* ",
+- "BUT "
+- ],
+- "examples": [
+- "EXAMPLZ"
+- ],
+- "feature": [
+- "OH HAI"
+- ],
+- "given": [
+- "* ",
+- "I CAN HAZ "
+- ],
+- "name": "LOLCAT",
+- "native": "LOLCAT",
+- "scenario": [
+- "MISHUN"
+- ],
+- "scenarioOutline": [
+- "MISHUN SRSLY"
+- ],
+- "then": [
+- "* ",
+- "DEN "
+- ],
+- "when": [
+- "* ",
+- "WEN "
+- ]
+- },
+- "en-old": {
+- "and": [
+- "* ",
+- "Ond ",
+- "7 "
+- ],
+- "background": [
+- "Aer",
+- "Ær"
+- ],
+- "but": [
+- "* ",
+- "Ac "
+- ],
+- "examples": [
+- "Se the",
+- "Se þe",
+- "Se ðe"
+- ],
+- "feature": [
+- "Hwaet",
+- "Hwæt"
+- ],
+- "given": [
+- "* ",
+- "Thurh ",
+- "Þurh ",
+- "Ðurh "
+- ],
+- "name": "Old English",
+- "native": "Englisc",
+- "scenario": [
+- "Swa"
+- ],
+- "scenarioOutline": [
+- "Swa hwaer swa",
+- "Swa hwær swa"
+- ],
+- "then": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða ",
+- "Tha the ",
+- "Þa þe ",
+- "Ða ðe "
+- ],
+- "when": [
+- "* ",
+- "Tha ",
+- "Þa ",
+- "Ða "
+- ]
+- },
+- "en-pirate": {
+- "and": [
+- "* ",
+- "Aye "
+- ],
+- "background": [
+- "Yo-ho-ho"
+- ],
+- "but": [
+- "* ",
+- "Avast! "
+- ],
+- "examples": [
+- "Dead men tell no tales"
+- ],
+- "feature": [
+- "Ahoy matey!"
+- ],
+- "given": [
+- "* ",
+- "Gangway! "
+- ],
+- "name": "Pirate",
+- "native": "Pirate",
+- "scenario": [
+- "Heave to"
+- ],
+- "scenarioOutline": [
+- "Shiver me timbers"
+- ],
+- "then": [
+- "* ",
+- "Let go and haul "
+- ],
+- "when": [
+- "* ",
+- "Blimey! "
+- ]
+- },
+- "eo": {
+- "and": [
+- "* ",
+- "Kaj "
+- ],
+- "background": [
+- "Fono"
+- ],
+- "but": [
+- "* ",
+- "Sed "
+- ],
+- "examples": [
+- "Ekzemploj"
+- ],
+- "feature": [
+- "Trajto"
+- ],
+- "given": [
+- "* ",
+- "Donitaĵo ",
+- "Komence "
+- ],
+- "name": "Esperanto",
+- "native": "Esperanto",
+- "scenario": [
+- "Scenaro",
+- "Kazo"
+- ],
+- "scenarioOutline": [
+- "Konturo de la scenaro",
+- "Skizo",
+- "Kazo-skizo"
+- ],
+- "then": [
+- "* ",
+- "Do "
+- ],
+- "when": [
+- "* ",
+- "Se "
+- ]
+- },
+- "es": {
+- "and": [
+- "* ",
+- "Y ",
+- "E "
+- ],
+- "background": [
+- "Antecedentes"
+- ],
+- "but": [
+- "* ",
+- "Pero "
+- ],
+- "examples": [
+- "Ejemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Spanish",
+- "native": "español",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esquema del escenario"
+- ],
+- "then": [
+- "* ",
+- "Entonces "
+- ],
+- "when": [
+- "* ",
+- "Cuando "
+- ]
+- },
+- "et": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Taust"
+- ],
+- "but": [
+- "* ",
+- "Kuid "
+- ],
+- "examples": [
+- "Juhtumid"
+- ],
+- "feature": [
+- "Omadus"
+- ],
+- "given": [
+- "* ",
+- "Eeldades "
+- ],
+- "name": "Estonian",
+- "native": "eesti keel",
+- "scenario": [
+- "Stsenaarium"
+- ],
+- "scenarioOutline": [
+- "Raamstsenaarium"
+- ],
+- "then": [
+- "* ",
+- "Siis "
+- ],
+- "when": [
+- "* ",
+- "Kui "
+- ]
+- },
+- "fa": {
+- "and": [
+- "* ",
+- "و "
+- ],
+- "background": [
+- "زمینه"
+- ],
+- "but": [
+- "* ",
+- "اما "
+- ],
+- "examples": [
+- "نمونه ها"
+- ],
+- "feature": [
+- "وِیژگی"
+- ],
+- "given": [
+- "* ",
+- "با فرض "
+- ],
+- "name": "Persian",
+- "native": "فارسی",
+- "scenario": [
+- "سناریو"
+- ],
+- "scenarioOutline": [
+- "الگوی سناریو"
+- ],
+- "then": [
+- "* ",
+- "آنگاه "
+- ],
+- "when": [
+- "* ",
+- "هنگامی "
+- ]
+- },
+- "fi": {
+- "and": [
+- "* ",
+- "Ja "
+- ],
+- "background": [
+- "Tausta"
+- ],
+- "but": [
+- "* ",
+- "Mutta "
+- ],
+- "examples": [
+- "Tapaukset"
+- ],
+- "feature": [
+- "Ominaisuus"
+- ],
+- "given": [
+- "* ",
+- "Oletetaan "
+- ],
+- "name": "Finnish",
+- "native": "suomi",
+- "scenario": [
+- "Tapaus"
+- ],
+- "scenarioOutline": [
+- "Tapausaihio"
+- ],
+- "then": [
+- "* ",
+- "Niin "
+- ],
+- "when": [
+- "* ",
+- "Kun "
+- ]
+- },
+- "fr": {
+- "and": [
+- "* ",
+- "Et que ",
+- "Et qu'",
+- "Et "
+- ],
+- "background": [
+- "Contexte"
+- ],
+- "but": [
+- "* ",
+- "Mais que ",
+- "Mais qu'",
+- "Mais "
+- ],
+- "examples": [
+- "Exemples"
+- ],
+- "feature": [
+- "Fonctionnalité"
+- ],
+- "given": [
+- "* ",
+- "Soit ",
+- "Etant donné que ",
+- "Etant donné qu'",
+- "Etant donné ",
+- "Etant donnée ",
+- "Etant donnés ",
+- "Etant données ",
+- "Étant donné que ",
+- "Étant donné qu'",
+- "Étant donné ",
+- "Étant donnée ",
+- "Étant donnés ",
+- "Étant données "
+- ],
+- "name": "French",
+- "native": "français",
+- "scenario": [
+- "Scénario"
+- ],
+- "scenarioOutline": [
+- "Plan du scénario",
+- "Plan du Scénario"
+- ],
+- "then": [
+- "* ",
+- "Alors "
+- ],
+- "when": [
+- "* ",
+- "Quand ",
+- "Lorsque ",
+- "Lorsqu'"
+- ]
+- },
+- "ga": {
+- "and": [
+- "* ",
+- "Agus"
+- ],
+- "background": [
+- "Cúlra"
+- ],
+- "but": [
+- "* ",
+- "Ach"
+- ],
+- "examples": [
+- "Samplaí"
+- ],
+- "feature": [
+- "Gné"
+- ],
+- "given": [
+- "* ",
+- "Cuir i gcás go",
+- "Cuir i gcás nach",
+- "Cuir i gcás gur",
+- "Cuir i gcás nár"
+- ],
+- "name": "Irish",
+- "native": "Gaeilge",
+- "scenario": [
+- "Cás"
+- ],
+- "scenarioOutline": [
+- "Cás Achomair"
+- ],
+- "then": [
+- "* ",
+- "Ansin"
+- ],
+- "when": [
+- "* ",
+- "Nuair a",
+- "Nuair nach",
+- "Nuair ba",
+- "Nuair nár"
+- ]
+- },
+- "gj": {
+- "and": [
+- "* ",
+- "અને "
+- ],
+- "background": [
+- "બેકગ્રાઉન્ડ"
+- ],
+- "but": [
+- "* ",
+- "પણ "
+- ],
+- "examples": [
+- "ઉદાહરણો"
+- ],
+- "feature": [
+- "લક્ષણ",
+- "વ્યાપાર જરૂર",
+- "ક્ષમતા"
+- ],
+- "given": [
+- "* ",
+- "આપેલ છે "
+- ],
+- "name": "Gujarati",
+- "native": "ગુજરાતી",
+- "scenario": [
+- "સ્થિતિ"
+- ],
+- "scenarioOutline": [
+- "પરિદ્દશ્ય રૂપરેખા",
+- "પરિદ્દશ્ય ઢાંચો"
+- ],
+- "then": [
+- "* ",
+- "પછી "
+- ],
+- "when": [
+- "* ",
+- "ક્યારે "
+- ]
+- },
+- "gl": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto"
+- ],
+- "but": [
+- "* ",
+- "Mais ",
+- "Pero "
+- ],
+- "examples": [
+- "Exemplos"
+- ],
+- "feature": [
+- "Característica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Galician",
+- "native": "galego",
+- "scenario": [
+- "Escenario"
+- ],
+- "scenarioOutline": [
+- "Esbozo do escenario"
+- ],
+- "then": [
+- "* ",
+- "Entón ",
+- "Logo "
+- ],
+- "when": [
+- "* ",
+- "Cando "
+- ]
+- },
+- "he": {
+- "and": [
+- "* ",
+- "וגם "
+- ],
+- "background": [
+- "רקע"
+- ],
+- "but": [
+- "* ",
+- "אבל "
+- ],
+- "examples": [
+- "דוגמאות"
+- ],
+- "feature": [
+- "תכונה"
+- ],
+- "given": [
+- "* ",
+- "בהינתן "
+- ],
+- "name": "Hebrew",
+- "native": "עברית",
+- "scenario": [
+- "תרחיש"
+- ],
+- "scenarioOutline": [
+- "תבנית תרחיש"
+- ],
+- "then": [
+- "* ",
+- "אז ",
+- "אזי "
+- ],
+- "when": [
+- "* ",
+- "כאשר "
+- ]
+- },
+- "hi": {
+- "and": [
+- "* ",
+- "और ",
+- "तथा "
+- ],
+- "background": [
+- "पृष्ठभूमि"
+- ],
+- "but": [
+- "* ",
+- "पर ",
+- "परन्तु ",
+- "किन्तु "
+- ],
+- "examples": [
+- "उदाहरण"
+- ],
+- "feature": [
+- "रूप लेख"
+- ],
+- "given": [
+- "* ",
+- "अगर ",
+- "यदि ",
+- "चूंकि "
+- ],
+- "name": "Hindi",
+- "native": "हिंदी",
+- "scenario": [
+- "परिदृश्य"
+- ],
+- "scenarioOutline": [
+- "परिदृश्य रूपरेखा"
+- ],
+- "then": [
+- "* ",
+- "तब ",
+- "तदा "
+- ],
+- "when": [
+- "* ",
+- "जब ",
+- "कदा "
+- ]
+- },
+- "hr": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primjeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Osobina",
+- "Mogućnost",
+- "Mogucnost"
+- ],
+- "given": [
+- "* ",
+- "Zadan ",
+- "Zadani ",
+- "Zadano "
+- ],
+- "name": "Croatian",
+- "native": "hrvatski",
+- "scenario": [
+- "Scenarij"
+- ],
+- "scenarioOutline": [
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "ht": {
+- "and": [
+- "* ",
+- "Ak ",
+- "Epi ",
+- "E "
+- ],
+- "background": [
+- "Kontèks",
+- "Istorik"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Egzanp"
+- ],
+- "feature": [
+- "Karakteristik",
+- "Mak",
+- "Fonksyonalite"
+- ],
+- "given": [
+- "* ",
+- "Sipoze ",
+- "Sipoze ke ",
+- "Sipoze Ke "
+- ],
+- "name": "Creole",
+- "native": "kreyòl",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Plan senaryo",
+- "Plan Senaryo",
+- "Senaryo deskripsyon",
+- "Senaryo Deskripsyon",
+- "Dyagram senaryo",
+- "Dyagram Senaryo"
+- ],
+- "then": [
+- "* ",
+- "Lè sa a ",
+- "Le sa a "
+- ],
+- "when": [
+- "* ",
+- "Lè ",
+- "Le "
+- ]
+- },
+- "hu": {
+- "and": [
+- "* ",
+- "És "
+- ],
+- "background": [
+- "Háttér"
+- ],
+- "but": [
+- "* ",
+- "De "
+- ],
+- "examples": [
+- "Példák"
+- ],
+- "feature": [
+- "Jellemző"
+- ],
+- "given": [
+- "* ",
+- "Amennyiben ",
+- "Adott "
+- ],
+- "name": "Hungarian",
+- "native": "magyar",
+- "scenario": [
+- "Forgatókönyv"
+- ],
+- "scenarioOutline": [
+- "Forgatókönyv vázlat"
+- ],
+- "then": [
+- "* ",
+- "Akkor "
+- ],
+- "when": [
+- "* ",
+- "Majd ",
+- "Ha ",
+- "Amikor "
+- ]
+- },
+- "id": {
+- "and": [
+- "* ",
+- "Dan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi "
+- ],
+- "examples": [
+- "Contoh"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Dengan "
+- ],
+- "name": "Indonesian",
+- "native": "Bahasa Indonesia",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Skenario konsep"
+- ],
+- "then": [
+- "* ",
+- "Maka "
+- ],
+- "when": [
+- "* ",
+- "Ketika "
+- ]
+- },
+- "is": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunnur"
+- ],
+- "but": [
+- "* ",
+- "En "
+- ],
+- "examples": [
+- "Dæmi",
+- "Atburðarásir"
+- ],
+- "feature": [
+- "Eiginleiki"
+- ],
+- "given": [
+- "* ",
+- "Ef "
+- ],
+- "name": "Icelandic",
+- "native": "Íslenska",
+- "scenario": [
+- "Atburðarás"
+- ],
+- "scenarioOutline": [
+- "Lýsing Atburðarásar",
+- "Lýsing Dæma"
+- ],
+- "then": [
+- "* ",
+- "Þá "
+- ],
+- "when": [
+- "* ",
+- "Þegar "
+- ]
+- },
+- "it": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contesto"
+- ],
+- "but": [
+- "* ",
+- "Ma "
+- ],
+- "examples": [
+- "Esempi"
+- ],
+- "feature": [
+- "Funzionalità"
+- ],
+- "given": [
+- "* ",
+- "Dato ",
+- "Data ",
+- "Dati ",
+- "Date "
+- ],
+- "name": "Italian",
+- "native": "italiano",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Schema dello scenario"
+- ],
+- "then": [
+- "* ",
+- "Allora "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ja": {
+- "and": [
+- "* ",
+- "かつ"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "しかし",
+- "但し",
+- "ただし"
+- ],
+- "examples": [
+- "例",
+- "サンプル"
+- ],
+- "feature": [
+- "フィーチャ",
+- "機能"
+- ],
+- "given": [
+- "* ",
+- "前提"
+- ],
+- "name": "Japanese",
+- "native": "日本語",
+- "scenario": [
+- "シナリオ"
+- ],
+- "scenarioOutline": [
+- "シナリオアウトライン",
+- "シナリオテンプレート",
+- "テンプレ",
+- "シナリオテンプレ"
+- ],
+- "then": [
+- "* ",
+- "ならば"
+- ],
+- "when": [
+- "* ",
+- "もし"
+- ]
+- },
+- "jv": {
+- "and": [
+- "* ",
+- "Lan "
+- ],
+- "background": [
+- "Dasar"
+- ],
+- "but": [
+- "* ",
+- "Tapi ",
+- "Nanging ",
+- "Ananging "
+- ],
+- "examples": [
+- "Conto",
+- "Contone"
+- ],
+- "feature": [
+- "Fitur"
+- ],
+- "given": [
+- "* ",
+- "Nalika ",
+- "Nalikaning "
+- ],
+- "name": "Javanese",
+- "native": "Basa Jawa",
+- "scenario": [
+- "Skenario"
+- ],
+- "scenarioOutline": [
+- "Konsep skenario"
+- ],
+- "then": [
+- "* ",
+- "Njuk ",
+- "Banjur "
+- ],
+- "when": [
+- "* ",
+- "Manawa ",
+- "Menawa "
+- ]
+- },
+- "ka": {
+- "and": [
+- "* ",
+- "და"
+- ],
+- "background": [
+- "კონტექსტი"
+- ],
+- "but": [
+- "* ",
+- "მაგრამ"
+- ],
+- "examples": [
+- "მაგალითები"
+- ],
+- "feature": [
+- "თვისება"
+- ],
+- "given": [
+- "* ",
+- "მოცემული"
+- ],
+- "name": "Georgian",
+- "native": "ქართველი",
+- "scenario": [
+- "სცენარის"
+- ],
+- "scenarioOutline": [
+- "სცენარის ნიმუში"
+- ],
+- "then": [
+- "* ",
+- "მაშინ"
+- ],
+- "when": [
+- "* ",
+- "როდესაც"
+- ]
+- },
+- "kn": {
+- "and": [
+- "* ",
+- "ಮತ್ತು "
+- ],
+- "background": [
+- "ಹಿನ್ನೆಲೆ"
+- ],
+- "but": [
+- "* ",
+- "ಆದರೆ "
+- ],
+- "examples": [
+- "ಉದಾಹರಣೆಗಳು"
+- ],
+- "feature": [
+- "ಹೆಚ್ಚಳ"
+- ],
+- "given": [
+- "* ",
+- "ನೀಡಿದ "
+- ],
+- "name": "Kannada",
+- "native": "ಕನ್ನಡ",
+- "scenario": [
+- "ಕಥಾಸಾರಾಂಶ"
+- ],
+- "scenarioOutline": [
+- "ವಿವರಣೆ"
+- ],
+- "then": [
+- "* ",
+- "ನಂತರ "
+- ],
+- "when": [
+- "* ",
+- "ಸ್ಥಿತಿಯನ್ನು "
+- ]
+- },
+- "ko": {
+- "and": [
+- "* ",
+- "그리고"
+- ],
+- "background": [
+- "배경"
+- ],
+- "but": [
+- "* ",
+- "하지만",
+- "단"
+- ],
+- "examples": [
+- "예"
+- ],
+- "feature": [
+- "기능"
+- ],
+- "given": [
+- "* ",
+- "조건",
+- "먼저"
+- ],
+- "name": "Korean",
+- "native": "한국어",
+- "scenario": [
+- "시나리오"
+- ],
+- "scenarioOutline": [
+- "시나리오 개요"
+- ],
+- "then": [
+- "* ",
+- "그러면"
+- ],
+- "when": [
+- "* ",
+- "만일",
+- "만약"
+- ]
+- },
+- "lt": {
+- "and": [
+- "* ",
+- "Ir "
+- ],
+- "background": [
+- "Kontekstas"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Pavyzdžiai",
+- "Scenarijai",
+- "Variantai"
+- ],
+- "feature": [
+- "Savybė"
+- ],
+- "given": [
+- "* ",
+- "Duota "
+- ],
+- "name": "Lithuanian",
+- "native": "lietuvių kalba",
+- "scenario": [
+- "Scenarijus"
+- ],
+- "scenarioOutline": [
+- "Scenarijaus šablonas"
+- ],
+- "then": [
+- "* ",
+- "Tada "
+- ],
+- "when": [
+- "* ",
+- "Kai "
+- ]
+- },
+- "lu": {
+- "and": [
+- "* ",
+- "an ",
+- "a "
+- ],
+- "background": [
+- "Hannergrond"
+- ],
+- "but": [
+- "* ",
+- "awer ",
+- "mä "
+- ],
+- "examples": [
+- "Beispiller"
+- ],
+- "feature": [
+- "Funktionalitéit"
+- ],
+- "given": [
+- "* ",
+- "ugeholl "
+- ],
+- "name": "Luxemburgish",
+- "native": "Lëtzebuergesch",
+- "scenario": [
+- "Szenario"
+- ],
+- "scenarioOutline": [
+- "Plang vum Szenario"
+- ],
+- "then": [
+- "* ",
+- "dann "
+- ],
+- "when": [
+- "* ",
+- "wann "
+- ]
+- },
+- "lv": {
+- "and": [
+- "* ",
+- "Un "
+- ],
+- "background": [
+- "Konteksts",
+- "Situācija"
+- ],
+- "but": [
+- "* ",
+- "Bet "
+- ],
+- "examples": [
+- "Piemēri",
+- "Paraugs"
+- ],
+- "feature": [
+- "Funkcionalitāte",
+- "Fīča"
+- ],
+- "given": [
+- "* ",
+- "Kad "
+- ],
+- "name": "Latvian",
+- "native": "latviešu",
+- "scenario": [
+- "Scenārijs"
+- ],
+- "scenarioOutline": [
+- "Scenārijs pēc parauga"
+- ],
+- "then": [
+- "* ",
+- "Tad "
+- ],
+- "when": [
+- "* ",
+- "Ja "
+- ]
+- },
+- "mk-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Содржина"
+- ],
+- "but": [
+- "* ",
+- "Но "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарија"
+- ],
+- "feature": [
+- "Функционалност",
+- "Бизнис потреба",
+- "Можност"
+- ],
+- "given": [
+- "* ",
+- "Дадено ",
+- "Дадена "
+- ],
+- "name": "Macedonian",
+- "native": "Македонски",
+- "scenario": [
+- "Сценарио",
+- "На пример"
+- ],
+- "scenarioOutline": [
+- "Преглед на сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Тогаш "
+- ],
+- "when": [
+- "* ",
+- "Кога "
+- ]
+- },
+- "mk-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Sodrzhina"
+- ],
+- "but": [
+- "* ",
+- "No "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenaria"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Biznis potreba",
+- "Mozhnost"
+- ],
+- "given": [
+- "* ",
+- "Dadeno ",
+- "Dadena "
+- ],
+- "name": "Macedonian (Latin)",
+- "native": "Makedonski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Na primer"
+- ],
+- "scenarioOutline": [
+- "Pregled na scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Togash "
+- ],
+- "when": [
+- "* ",
+- "Koga "
+- ]
+- },
+- "mn": {
+- "and": [
+- "* ",
+- "Мөн ",
+- "Тэгээд "
+- ],
+- "background": [
+- "Агуулга"
+- ],
+- "but": [
+- "* ",
+- "Гэхдээ ",
+- "Харин "
+- ],
+- "examples": [
+- "Тухайлбал"
+- ],
+- "feature": [
+- "Функц",
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Өгөгдсөн нь ",
+- "Анх "
+- ],
+- "name": "Mongolian",
+- "native": "монгол",
+- "scenario": [
+- "Сценар"
+- ],
+- "scenarioOutline": [
+- "Сценарын төлөвлөгөө"
+- ],
+- "then": [
+- "* ",
+- "Тэгэхэд ",
+- "Үүний дараа "
+- ],
+- "when": [
+- "* ",
+- "Хэрэв "
+- ]
+- },
+- "nl": {
+- "and": [
+- "* ",
+- "En "
+- ],
+- "background": [
+- "Achtergrond"
+- ],
+- "but": [
+- "* ",
+- "Maar "
+- ],
+- "examples": [
+- "Voorbeelden"
+- ],
+- "feature": [
+- "Functionaliteit"
+- ],
+- "given": [
+- "* ",
+- "Gegeven ",
+- "Stel "
+- ],
+- "name": "Dutch",
+- "native": "Nederlands",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstract Scenario"
+- ],
+- "then": [
+- "* ",
+- "Dan "
+- ],
+- "when": [
+- "* ",
+- "Als ",
+- "Wanneer "
+- ]
+- },
+- "no": {
+- "and": [
+- "* ",
+- "Og "
+- ],
+- "background": [
+- "Bakgrunn"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Eksempler"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Gitt "
+- ],
+- "name": "Norwegian",
+- "native": "norsk",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Scenariomal",
+- "Abstrakt Scenario"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "Når "
+- ]
+- },
+- "pa": {
+- "and": [
+- "* ",
+- "ਅਤੇ "
+- ],
+- "background": [
+- "ਪਿਛੋਕੜ"
+- ],
+- "but": [
+- "* ",
+- "ਪਰ "
+- ],
+- "examples": [
+- "ਉਦਾਹਰਨਾਂ"
+- ],
+- "feature": [
+- "ਖਾਸੀਅਤ",
+- "ਮੁਹਾਂਦਰਾ",
+- "ਨਕਸ਼ ਨੁਹਾਰ"
+- ],
+- "given": [
+- "* ",
+- "ਜੇਕਰ ",
+- "ਜਿਵੇਂ ਕਿ "
+- ],
+- "name": "Panjabi",
+- "native": "ਪੰਜਾਬੀ",
+- "scenario": [
+- "ਪਟਕਥਾ"
+- ],
+- "scenarioOutline": [
+- "ਪਟਕਥਾ ਢਾਂਚਾ",
+- "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ"
+- ],
+- "then": [
+- "* ",
+- "ਤਦ "
+- ],
+- "when": [
+- "* ",
+- "ਜਦੋਂ "
+- ]
+- },
+- "pl": {
+- "and": [
+- "* ",
+- "Oraz ",
+- "I "
+- ],
+- "background": [
+- "Założenia"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Przykłady"
+- ],
+- "feature": [
+- "Właściwość",
+- "Funkcja",
+- "Aspekt",
+- "Potrzeba biznesowa"
+- ],
+- "given": [
+- "* ",
+- "Zakładając ",
+- "Mając ",
+- "Zakładając, że "
+- ],
+- "name": "Polish",
+- "native": "polski",
+- "scenario": [
+- "Scenariusz"
+- ],
+- "scenarioOutline": [
+- "Szablon scenariusza"
+- ],
+- "then": [
+- "* ",
+- "Wtedy "
+- ],
+- "when": [
+- "* ",
+- "Jeżeli ",
+- "Jeśli ",
+- "Gdy ",
+- "Kiedy "
+- ]
+- },
+- "pt": {
+- "and": [
+- "* ",
+- "E "
+- ],
+- "background": [
+- "Contexto",
+- "Cenário de Fundo",
+- "Cenario de Fundo",
+- "Fundo"
+- ],
+- "but": [
+- "* ",
+- "Mas "
+- ],
+- "examples": [
+- "Exemplos",
+- "Cenários",
+- "Cenarios"
+- ],
+- "feature": [
+- "Funcionalidade",
+- "Característica",
+- "Caracteristica"
+- ],
+- "given": [
+- "* ",
+- "Dado ",
+- "Dada ",
+- "Dados ",
+- "Dadas "
+- ],
+- "name": "Portuguese",
+- "native": "português",
+- "scenario": [
+- "Cenário",
+- "Cenario"
+- ],
+- "scenarioOutline": [
+- "Esquema do Cenário",
+- "Esquema do Cenario",
+- "Delineação do Cenário",
+- "Delineacao do Cenario"
+- ],
+- "then": [
+- "* ",
+- "Então ",
+- "Entao "
+- ],
+- "when": [
+- "* ",
+- "Quando "
+- ]
+- },
+- "ro": {
+- "and": [
+- "* ",
+- "Si ",
+- "Și ",
+- "Şi "
+- ],
+- "background": [
+- "Context"
+- ],
+- "but": [
+- "* ",
+- "Dar "
+- ],
+- "examples": [
+- "Exemple"
+- ],
+- "feature": [
+- "Functionalitate",
+- "Funcționalitate",
+- "Funcţionalitate"
+- ],
+- "given": [
+- "* ",
+- "Date fiind ",
+- "Dat fiind ",
+- "Dată fiind",
+- "Dati fiind ",
+- "Dați fiind ",
+- "Daţi fiind "
+- ],
+- "name": "Romanian",
+- "native": "română",
+- "scenario": [
+- "Scenariu"
+- ],
+- "scenarioOutline": [
+- "Structura scenariu",
+- "Structură scenariu"
+- ],
+- "then": [
+- "* ",
+- "Atunci "
+- ],
+- "when": [
+- "* ",
+- "Cand ",
+- "Când "
+- ]
+- },
+- "ru": {
+- "and": [
+- "* ",
+- "И ",
+- "К тому же ",
+- "Также "
+- ],
+- "background": [
+- "Предыстория",
+- "Контекст"
+- ],
+- "but": [
+- "* ",
+- "Но ",
+- "А "
+- ],
+- "examples": [
+- "Примеры"
+- ],
+- "feature": [
+- "Функция",
+- "Функциональность",
+- "Функционал",
+- "Свойство"
+- ],
+- "given": [
+- "* ",
+- "Допустим ",
+- "Дано ",
+- "Пусть ",
+- "Если "
+- ],
+- "name": "Russian",
+- "native": "русский",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Структура сценария"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Затем ",
+- "Тогда "
+- ],
+- "when": [
+- "* ",
+- "Когда "
+- ]
+- },
+- "sk": {
+- "and": [
+- "* ",
+- "A ",
+- "A tiež ",
+- "A taktiež ",
+- "A zároveň "
+- ],
+- "background": [
+- "Pozadie"
+- ],
+- "but": [
+- "* ",
+- "Ale "
+- ],
+- "examples": [
+- "Príklady"
+- ],
+- "feature": [
+- "Požiadavka",
+- "Funkcia",
+- "Vlastnosť"
+- ],
+- "given": [
+- "* ",
+- "Pokiaľ ",
+- "Za predpokladu "
+- ],
+- "name": "Slovak",
+- "native": "Slovensky",
+- "scenario": [
+- "Scenár"
+- ],
+- "scenarioOutline": [
+- "Náčrt Scenáru",
+- "Náčrt Scenára",
+- "Osnova Scenára"
+- ],
+- "then": [
+- "* ",
+- "Tak ",
+- "Potom "
+- ],
+- "when": [
+- "* ",
+- "Keď ",
+- "Ak "
+- ]
+- },
+- "sl": {
+- "and": [
+- "In ",
+- "Ter "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Ozadje"
+- ],
+- "but": [
+- "Toda ",
+- "Ampak ",
+- "Vendar "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Funkcija",
+- "Možnosti",
+- "Moznosti",
+- "Lastnost",
+- "Značilnost"
+- ],
+- "given": [
+- "Dano ",
+- "Podano ",
+- "Zaradi ",
+- "Privzeto "
+- ],
+- "name": "Slovenian",
+- "native": "Slovenski",
+- "scenario": [
+- "Scenarij",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept",
+- "Oris scenarija",
+- "Osnutek"
+- ],
+- "then": [
+- "Nato ",
+- "Potem ",
+- "Takrat "
+- ],
+- "when": [
+- "Ko ",
+- "Ce ",
+- "Če ",
+- "Kadar "
+- ]
+- },
+- "sr-Cyrl": {
+- "and": [
+- "* ",
+- "И "
+- ],
+- "background": [
+- "Контекст",
+- "Основа",
+- "Позадина"
+- ],
+- "but": [
+- "* ",
+- "Али "
+- ],
+- "examples": [
+- "Примери",
+- "Сценарији"
+- ],
+- "feature": [
+- "Функционалност",
+- "Могућност",
+- "Особина"
+- ],
+- "given": [
+- "* ",
+- "За дато ",
+- "За дате ",
+- "За дати "
+- ],
+- "name": "Serbian",
+- "native": "Српски",
+- "scenario": [
+- "Сценарио",
+- "Пример"
+- ],
+- "scenarioOutline": [
+- "Структура сценарија",
+- "Скица",
+- "Концепт"
+- ],
+- "then": [
+- "* ",
+- "Онда "
+- ],
+- "when": [
+- "* ",
+- "Када ",
+- "Кад "
+- ]
+- },
+- "sr-Latn": {
+- "and": [
+- "* ",
+- "I "
+- ],
+- "background": [
+- "Kontekst",
+- "Osnova",
+- "Pozadina"
+- ],
+- "but": [
+- "* ",
+- "Ali "
+- ],
+- "examples": [
+- "Primeri",
+- "Scenariji"
+- ],
+- "feature": [
+- "Funkcionalnost",
+- "Mogućnost",
+- "Mogucnost",
+- "Osobina"
+- ],
+- "given": [
+- "* ",
+- "Za dato ",
+- "Za date ",
+- "Za dati "
+- ],
+- "name": "Serbian (Latin)",
+- "native": "Srpski (Latinica)",
+- "scenario": [
+- "Scenario",
+- "Primer"
+- ],
+- "scenarioOutline": [
+- "Struktura scenarija",
+- "Skica",
+- "Koncept"
+- ],
+- "then": [
+- "* ",
+- "Onda "
+- ],
+- "when": [
+- "* ",
+- "Kada ",
+- "Kad "
+- ]
+- },
+- "sv": {
+- "and": [
+- "* ",
+- "Och "
+- ],
+- "background": [
+- "Bakgrund"
+- ],
+- "but": [
+- "* ",
+- "Men "
+- ],
+- "examples": [
+- "Exempel"
+- ],
+- "feature": [
+- "Egenskap"
+- ],
+- "given": [
+- "* ",
+- "Givet "
+- ],
+- "name": "Swedish",
+- "native": "Svenska",
+- "scenario": [
+- "Scenario"
+- ],
+- "scenarioOutline": [
+- "Abstrakt Scenario",
+- "Scenariomall"
+- ],
+- "then": [
+- "* ",
+- "Så "
+- ],
+- "when": [
+- "* ",
+- "När "
+- ]
+- },
+- "ta": {
+- "and": [
+- "* ",
+- "மேலும் ",
+- "மற்றும் "
+- ],
+- "background": [
+- "பின்னணி"
+- ],
+- "but": [
+- "* ",
+- "ஆனால் "
+- ],
+- "examples": [
+- "எடுத்துக்காட்டுகள்",
+- "காட்சிகள்",
+- " நிலைமைகளில்"
+- ],
+- "feature": [
+- "அம்சம்",
+- "வணிக தேவை",
+- "திறன்"
+- ],
+- "given": [
+- "* ",
+- "கொடுக்கப்பட்ட "
+- ],
+- "name": "Tamil",
+- "native": "தமிழ்",
+- "scenario": [
+- "காட்சி"
+- ],
+- "scenarioOutline": [
+- "காட்சி சுருக்கம்",
+- "காட்சி வார்ப்புரு"
+- ],
+- "then": [
+- "* ",
+- "அப்பொழுது "
+- ],
+- "when": [
+- "* ",
+- "எப்போது "
+- ]
+- },
+- "th": {
+- "and": [
+- "* ",
+- "และ "
+- ],
+- "background": [
+- "แนวคิด"
+- ],
+- "but": [
+- "* ",
+- "แต่ "
+- ],
+- "examples": [
+- "ชุดของตัวอย่าง",
+- "ชุดของเหตุการณ์"
+- ],
+- "feature": [
+- "โครงหลัก",
+- "ความต้องการทางธุรกิจ",
+- "ความสามารถ"
+- ],
+- "given": [
+- "* ",
+- "กำหนดให้ "
+- ],
+- "name": "Thai",
+- "native": "ไทย",
+- "scenario": [
+- "เหตุการณ์"
+- ],
+- "scenarioOutline": [
+- "สรุปเหตุการณ์",
+- "โครงสร้างของเหตุการณ์"
+- ],
+- "then": [
+- "* ",
+- "ดังนั้น "
+- ],
+- "when": [
+- "* ",
+- "เมื่อ "
+- ]
+- },
+- "tl": {
+- "and": [
+- "* ",
+- "మరియు "
+- ],
+- "background": [
+- "నేపథ్యం"
+- ],
+- "but": [
+- "* ",
+- "కాని "
+- ],
+- "examples": [
+- "ఉదాహరణలు"
+- ],
+- "feature": [
+- "గుణము"
+- ],
+- "given": [
+- "* ",
+- "చెప్పబడినది "
+- ],
+- "name": "Telugu",
+- "native": "తెలుగు",
+- "scenario": [
+- "సన్నివేశం"
+- ],
+- "scenarioOutline": [
+- "కథనం"
+- ],
+- "then": [
+- "* ",
+- "అప్పుడు "
+- ],
+- "when": [
+- "* ",
+- "ఈ పరిస్థితిలో "
+- ]
+- },
+- "tlh": {
+- "and": [
+- "* ",
+- "'ej ",
+- "latlh "
+- ],
+- "background": [
+- "mo'"
+- ],
+- "but": [
+- "* ",
+- "'ach ",
+- "'a "
+- ],
+- "examples": [
+- "ghantoH",
+- "lutmey"
+- ],
+- "feature": [
+- "Qap",
+- "Qu'meH 'ut",
+- "perbogh",
+- "poQbogh malja'",
+- "laH"
+- ],
+- "given": [
+- "* ",
+- "ghu' noblu' ",
+- "DaH ghu' bejlu' "
+- ],
+- "name": "Klingon",
+- "native": "tlhIngan",
+- "scenario": [
+- "lut"
+- ],
+- "scenarioOutline": [
+- "lut chovnatlh"
+- ],
+- "then": [
+- "* ",
+- "vaj "
+- ],
+- "when": [
+- "* ",
+- "qaSDI' "
+- ]
+- },
+- "tr": {
+- "and": [
+- "* ",
+- "Ve "
+- ],
+- "background": [
+- "Geçmiş"
+- ],
+- "but": [
+- "* ",
+- "Fakat ",
+- "Ama "
+- ],
+- "examples": [
+- "Örnekler"
+- ],
+- "feature": [
+- "Özellik"
+- ],
+- "given": [
+- "* ",
+- "Diyelim ki "
+- ],
+- "name": "Turkish",
+- "native": "Türkçe",
+- "scenario": [
+- "Senaryo"
+- ],
+- "scenarioOutline": [
+- "Senaryo taslağı"
+- ],
+- "then": [
+- "* ",
+- "O zaman "
+- ],
+- "when": [
+- "* ",
+- "Eğer ki "
+- ]
+- },
+- "tt": {
+- "and": [
+- "* ",
+- "Һәм ",
+- "Вә "
+- ],
+- "background": [
+- "Кереш"
+- ],
+- "but": [
+- "* ",
+- "Ләкин ",
+- "Әмма "
+- ],
+- "examples": [
+- "Үрнәкләр",
+- "Мисаллар"
+- ],
+- "feature": [
+- "Мөмкинлек",
+- "Үзенчәлеклелек"
+- ],
+- "given": [
+- "* ",
+- "Әйтик "
+- ],
+- "name": "Tatar",
+- "native": "Татарча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарийның төзелеше"
+- ],
+- "then": [
+- "* ",
+- "Нәтиҗәдә "
+- ],
+- "when": [
+- "* ",
+- "Әгәр "
+- ]
+- },
+- "uk": {
+- "and": [
+- "* ",
+- "І ",
+- "А також ",
+- "Та "
+- ],
+- "background": [
+- "Передумова"
+- ],
+- "but": [
+- "* ",
+- "Але "
+- ],
+- "examples": [
+- "Приклади"
+- ],
+- "feature": [
+- "Функціонал"
+- ],
+- "given": [
+- "* ",
+- "Припустимо ",
+- "Припустимо, що ",
+- "Нехай ",
+- "Дано "
+- ],
+- "name": "Ukrainian",
+- "native": "Українська",
+- "scenario": [
+- "Сценарій"
+- ],
+- "scenarioOutline": [
+- "Структура сценарію"
+- ],
+- "then": [
+- "* ",
+- "То ",
+- "Тоді "
+- ],
+- "when": [
+- "* ",
+- "Якщо ",
+- "Коли "
+- ]
+- },
+- "ur": {
+- "and": [
+- "* ",
+- "اور "
+- ],
+- "background": [
+- "پس منظر"
+- ],
+- "but": [
+- "* ",
+- "لیکن "
+- ],
+- "examples": [
+- "مثالیں"
+- ],
+- "feature": [
+- "صلاحیت",
+- "کاروبار کی ضرورت",
+- "خصوصیت"
+- ],
+- "given": [
+- "* ",
+- "اگر ",
+- "بالفرض ",
+- "فرض کیا "
+- ],
+- "name": "Urdu",
+- "native": "اردو",
+- "scenario": [
+- "منظرنامہ"
+- ],
+- "scenarioOutline": [
+- "منظر نامے کا خاکہ"
+- ],
+- "then": [
+- "* ",
+- "پھر ",
+- "تب "
+- ],
+- "when": [
+- "* ",
+- "جب "
+- ]
+- },
+- "uz": {
+- "and": [
+- "* ",
+- "Ва "
+- ],
+- "background": [
+- "Тарих"
+- ],
+- "but": [
+- "* ",
+- "Лекин ",
+- "Бирок ",
+- "Аммо "
+- ],
+- "examples": [
+- "Мисоллар"
+- ],
+- "feature": [
+- "Функционал"
+- ],
+- "given": [
+- "* ",
+- "Агар "
+- ],
+- "name": "Uzbek",
+- "native": "Узбекча",
+- "scenario": [
+- "Сценарий"
+- ],
+- "scenarioOutline": [
+- "Сценарий структураси"
+- ],
+- "then": [
+- "* ",
+- "Унда "
+- ],
+- "when": [
+- "* ",
+- "Агар "
+- ]
+- },
+- "vi": {
+- "and": [
+- "* ",
+- "Và "
+- ],
+- "background": [
+- "Bối cảnh"
+- ],
+- "but": [
+- "* ",
+- "Nhưng "
+- ],
+- "examples": [
+- "Dữ liệu"
+- ],
+- "feature": [
+- "Tính năng"
+- ],
+- "given": [
+- "* ",
+- "Biết ",
+- "Cho "
+- ],
+- "name": "Vietnamese",
+- "native": "Tiếng Việt",
+- "scenario": [
+- "Tình huống",
+- "Kịch bản"
+- ],
+- "scenarioOutline": [
+- "Khung tình huống",
+- "Khung kịch bản"
+- ],
+- "then": [
+- "* ",
+- "Thì "
+- ],
+- "when": [
+- "* ",
+- "Khi "
+- ]
+- },
+- "zh-CN": {
+- "and": [
+- "* ",
+- "而且",
+- "并且",
+- "同时"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假设",
+- "假定"
+- ],
+- "name": "Chinese simplified",
+- "native": "简体中文",
+- "scenario": [
+- "场景",
+- "剧本"
+- ],
+- "scenarioOutline": [
+- "场景大纲",
+- "剧本大纲"
+- ],
+- "then": [
+- "* ",
+- "那么"
+- ],
+- "when": [
+- "* ",
+- "当"
+- ]
+- },
+- "zh-TW": {
+- "and": [
+- "* ",
+- "而且",
+- "並且",
+- "同時"
+- ],
+- "background": [
+- "背景"
+- ],
+- "but": [
+- "* ",
+- "但是"
+- ],
+- "examples": [
+- "例子"
+- ],
+- "feature": [
+- "功能"
+- ],
+- "given": [
+- "* ",
+- "假如",
+- "假設",
+- "假定"
+- ],
+- "name": "Chinese traditional",
+- "native": "繁體中文",
+- "scenario": [
+- "場景",
+- "劇本"
+- ],
+- "scenarioOutline": [
+- "場景大綱",
+- "劇本大綱"
+- ],
+- "then": [
+- "* ",
+- "那麼"
+- ],
+- "when": [
+- "* ",
+- "當"
+- ]
+- }
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
new file mode 100644
index 000000000..926fc7233
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
@@ -0,0 +1,736 @@
+From 1983740f7df60c4f1795e81c9cc9b4c33200a594 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:21:27 +0200
+Subject: [PATCH] CLEANUP: Remove deprecated parts.
+
+---
+ bin/convert_i18n_yaml.py | 77 -----
+ bin/i18n.yml | 635 ---------------------------------------
+ 2 files changed, 712 deletions(-)
+ delete mode 100755 bin/convert_i18n_yaml.py
+ delete mode 100644 bin/i18n.yml
+
+diff --git a/bin/convert_i18n_yaml.py b/bin/convert_i18n_yaml.py
+deleted file mode 100755
+index d6a6713..0000000
+--- a/bin/convert_i18n_yaml.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-#!/usr/bin/env python
+-# -*- coding: UTF-8 -*-
+-# USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py
+-"""
+-Generates I18N python module based on YAML description (i18n.yml).
+-
+-REQUIRES:
+- * argparse
+- * six
+- * PyYAML
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import argparse
+-import os.path
+-import six
+-import sys
+-import pprint
+-import yaml
+-
+-HERE = os.path.dirname(__file__)
+-NAME = os.path.basename(__file__)
+-__version__ = "1.0"
+-
+-def yaml_normalize(data):
+- for part in data:
+- keywords = data[part]
+- for k in keywords:
+- v = keywords[k]
+- # bloody YAML parser returns a mixture of unicode and str
+- if not isinstance(v, six.text_type):
+- v = v.decode("UTF-8")
+- keywords[k] = v.split("|")
+- return data
+-
+-def main(args=None):
+- if args is None:
+- args = sys.argv[1:]
+- parser = argparse.ArgumentParser(prog=NAME,
+- description="Generate python module i18n from YAML based data")
+- parser.add_argument("-d", "--data", dest="yaml_file",
+- default=os.path.join(HERE, "i18n.yml"),
+- help="Path to i18n.yml file (YAML file).")
+- parser.add_argument("output_file", default="stdout",
+- help="Filename of Python I18N module (as output).")
+- parser.add_argument("--version", action="version", version=__version__)
+-
+- options = parser.parse_args(args)
+- if not os.path.isfile(options.yaml_file):
+- parser.error("YAML file not found: %s" % options.yaml_file)
+-
+- # -- STEP 1: Load YAML data.
+- languages = yaml.load(open(options.yaml_file))
+- languages = yaml_normalize(languages)
+-
+- # -- STEP 2: Generate python module with i18n data.
+- contents = u"""# -*- coding: UTF-8 -*-
+-# -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml
+-# pylint: disable=line-too-long
+-
+-languages = \\
+-"""
+- if options.output_file in ("-", "stdout"):
+- i18n_py = sys.stdout
+- should_close = False
+- else:
+- i18n_py = open(options.output_file, "w")
+- should_close = True
+- i18n_py.write(contents.encode("UTF-8"))
+- i18n_py.write(pprint.pformat(languages).encode("UTF-8"))
+- i18n_py.write(u"\n")
+- if should_close:
+- i18n_py.close()
+- return 0
+-
+-if __name__ == "__main__":
+- sys.exit(main())
+diff --git a/bin/i18n.yml b/bin/i18n.yml
+deleted file mode 100644
+index 82345a4..0000000
+--- a/bin/i18n.yml
++++ /dev/null
+@@ -1,635 +0,0 @@
+-# encoding: UTF-8
+-#
+-# We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable):
+-# http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+-# http://en.wikipedia.org/wiki/ISO_3166-1
+-#
+-# If you want several aliases for a keyword, just separate them
+-# with a | character. The * is a step keyword alias for all translations.
+-#
+-# If you do *not* want a trailing space after a keyword, end it with a < character.
+-# (See Chinese for examples).
+-#
+-# This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
+-#
+-# 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.
+-
+-"en":
+- name: English
+- native: English
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: Scenario Outline|Scenario Template
+- examples: Examples|Scenarios
+- given: "*|Given"
+- when: "*|When"
+- then: "*|Then"
+- and: "*|And"
+- but: "*|But"
+-
+-# Please keep the grammars in alphabetical order by name from here and down.
+-
+-"ar":
+- name: Arabic
+- native: العربية
+- feature: خاصية
+- background: الخلفية
+- scenario: سيناريو
+- scenario_outline: سيناريو مخطط
+- examples: امثلة
+- given: "*|بفرض"
+- when: "*|متى|عندما"
+- then: "*|اذاً|ثم"
+- and: "*|و"
+- but: "*|لكن"
+-"bg":
+- name: Bulgarian
+- native: български
+- feature: Функционалност
+- background: Предистория
+- scenario: Сценарий
+- scenario_outline: Рамка на сценарий
+- examples: Примери
+- given: "*|Дадено"
+- when: "*|Когато"
+- then: "*|То"
+- and: "*|И"
+- but: "*|Но"
+-"ca":
+- name: Catalan
+- native: català
+- background: Rerefons|Antecedents
+- feature: Característica|Funcionalitat
+- scenario: Escenari
+- scenario_outline: Esquema de l'escenari
+- examples: Exemples
+- given: "*|Donat|Donada|Atès|Atesa"
+- when: "*|Quan"
+- then: "*|Aleshores|Cal"
+- and: "*|I"
+- but: "*|Però"
+-"cy-GB":
+- name: Welsh
+- native: Cymraeg
+- background: Cefndir
+- feature: Arwedd
+- scenario: Scenario
+- scenario_outline: Scenario Amlinellol
+- examples: Enghreifftiau
+- given: "*|Anrhegedig a"
+- when: "*|Pryd"
+- then: "*|Yna"
+- and: "*|A"
+- but: "*|Ond"
+-"cs":
+- name: Czech
+- native: Česky
+- feature: Požadavek
+- background: Pozadí|Kontext
+- scenario: Scénář
+- scenario_outline: Náčrt Scénáře|Osnova scénáře
+- examples: Příklady
+- given: "*|Pokud|Za předpokladu"
+- when: "*|Když"
+- then: "*|Pak"
+- and: "*|A|A také"
+- but: "*|Ale"
+-"da":
+- name: Danish
+- native: dansk
+- feature: Egenskab
+- background: Baggrund
+- scenario: Scenarie
+- scenario_outline: Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Givet"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"de":
+- name: German
+- native: Deutsch
+- feature: Funktionalität
+- background: Grundlage
+- scenario: Szenario
+- scenario_outline: Szenariogrundriss
+- examples: Beispiele
+- given: "*|Angenommen|Gegeben sei"
+- when: "*|Wenn"
+- then: "*|Dann"
+- and: "*|Und"
+- but: "*|Aber"
+-"en-au":
+- name: Australian
+- native: Australian
+- feature: Crikey
+- background: Background
+- scenario: Mate
+- scenario_outline: Blokes
+- examples: Cobber
+- given: "*|Ya know how"
+- when: "*|When"
+- then: "*|Ya gotta"
+- and: "*|N"
+- but: "*|Cept"
+-"en-lol":
+- name: LOLCAT
+- native: LOLCAT
+- feature: OH HAI
+- background: B4
+- scenario: MISHUN
+- scenario_outline: MISHUN SRSLY
+- examples: EXAMPLZ
+- given: "*|I CAN HAZ"
+- when: "*|WEN"
+- then: "*|DEN"
+- and: "*|AN"
+- but: "*|BUT"
+-"en-pirate":
+- name: Pirate
+- native: Pirate
+- feature: Ahoy matey!
+- background: Yo-ho-ho
+- scenario: Heave to
+- scenario_outline: Shiver me timbers
+- examples: Dead men tell no tales
+- given: "*|Gangway!"
+- when: "*|Blimey!"
+- then: "*|Let go and haul"
+- and: "*|Aye"
+- but: "*|Avast!"
+-"en-Scouse":
+- name: Scouse
+- native: Scouse
+- feature: Feature
+- background: "Dis is what went down"
+- scenario: "The thing of it is"
+- scenario_outline: "Wharrimean is"
+- examples: Examples
+- given: "*|Givun|Youse know when youse got"
+- when: "*|Wun|Youse know like when"
+- then: "*|Dun|Den youse gotta"
+- and: "*|An"
+- but: "*|Buh"
+-"en-tx":
+- name: Texan
+- native: Texan
+- feature: Feature
+- background: Background
+- scenario: Scenario
+- scenario_outline: All y'all
+- examples: Examples
+- given: "*|Given y'all"
+- when: "*|When y'all"
+- then: "*|Then y'all"
+- and: "*|And y'all"
+- but: "*|But y'all"
+-"eo":
+- name: Esperanto
+- native: Esperanto
+- feature: Trajto
+- background: Fono
+- scenario: Scenaro
+- scenario_outline: Konturo de la scenaro
+- examples: Ekzemploj
+- given: "*|Donitaĵo"
+- when: "*|Se"
+- then: "*|Do"
+- and: "*|Kaj"
+- but: "*|Sed"
+-"es":
+- name: Spanish
+- native: español
+- background: Antecedentes
+- feature: Característica
+- scenario: Escenario
+- scenario_outline: Esquema del escenario
+- examples: Ejemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cuando"
+- then: "*|Entonces"
+- and: "*|Y"
+- but: "*|Pero"
+-"et":
+- name: Estonian
+- native: eesti keel
+- feature: Omadus
+- background: Taust
+- scenario: Stsenaarium
+- scenario_outline: Raamstsenaarium
+- examples: Juhtumid
+- given: "*|Eeldades"
+- when: "*|Kui"
+- then: "*|Siis"
+- and: "*|Ja"
+- but: "*|Kuid"
+-"fi":
+- name: Finnish
+- native: suomi
+- feature: Ominaisuus
+- background: Tausta
+- scenario: Tapaus
+- scenario_outline: Tapausaihio
+- examples: Tapaukset
+- given: "*|Oletetaan"
+- when: "*|Kun"
+- then: "*|Niin"
+- and: "*|Ja"
+- but: "*|Mutta"
+-"fr":
+- name: French
+- native: français
+- feature: Fonctionnalité
+- background: Contexte
+- scenario: Scénario
+- scenario_outline: Plan du scénario|Plan du Scénario
+- examples: Exemples
+- given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données"
+- when: "*|Quand|Lorsque|Lorsqu'<"
+- then: "*|Alors"
+- and: "*|Et"
+- but: "*|Mais"
+-"gl":
+- name: Galician
+- native: galego
+- feature: Característica
+- background: Contexto
+- scenario: Escenario
+- scenario_outline: "Esbozo do escenario"
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Cando"
+- then: "*|Entón|Logo"
+- and: "*|E"
+- but: "*|Mais|Pero"
+-
+-"he":
+- name: Hebrew
+- native: עברית
+- feature: תכונה
+- background: רקע
+- scenario: תרחיש
+- scenario_outline: תבנית תרחיש
+- examples: דוגמאות
+- given: "*|בהינתן"
+- when: "*|כאשר"
+- then: "*|אז|אזי"
+- and: "*|וגם"
+- but: "*|אבל"
+-"hr":
+- name: Croatian
+- native: hrvatski
+- feature: Osobina|Mogućnost|Mogucnost
+- background: Pozadina
+- scenario: Scenarij
+- scenario_outline: Skica|Koncept
+- examples: Primjeri|Scenariji
+- given: "*|Zadan|Zadani|Zadano"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"hu":
+- name: Hungarian
+- native: magyar
+- feature: Jellemző
+- background: Háttér
+- scenario: Forgatókönyv
+- scenario_outline: Forgatókönyv vázlat
+- examples: Példák
+- given: "*|Amennyiben|Adott"
+- when: "*|Majd|Ha|Amikor"
+- then: "*|Akkor"
+- and: "*|És"
+- but: "*|De"
+-"id":
+- name: Indonesian
+- native: Bahasa Indonesia
+- feature: Fitur
+- background: Dasar
+- scenario: Skenario
+- scenario_outline: Skenario konsep
+- examples: Contoh
+- given: "*|Dengan"
+- when: "*|Ketika"
+- then: "*|Maka"
+- and: "*|Dan"
+- but: "*|Tapi"
+-"is":
+- name: Icelandic
+- native: Íslenska
+- feature: Eiginleiki
+- background: Bakgrunnur
+- scenario: Atburðarás
+- scenario_outline: Lýsing Atburðarásar|Lýsing Dæma
+- examples: Dæmi|Atburðarásir
+- given: "*|Ef"
+- when: "*|Þegar"
+- then: "*|Þá"
+- and: "*|Og"
+- but: "*|En"
+-"it":
+- name: Italian
+- native: italiano
+- feature: Funzionalità
+- background: Contesto
+- scenario: Scenario
+- scenario_outline: Schema dello scenario
+- examples: Esempi
+- given: "*|Dato|Data|Dati|Date"
+- when: "*|Quando"
+- then: "*|Allora"
+- and: "*|E"
+- but: "*|Ma"
+-"ja":
+- name: Japanese
+- native: 日本語
+- feature: フィーチャ|機能
+- background: 背景
+- scenario: シナリオ
+- scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ
+- examples: 例|サンプル
+- given: "*|前提<"
+- when: "*|もし<"
+- then: "*|ならば<"
+- and: "*|かつ<"
+- but: "*|しかし<|但し<|ただし<"
+-"ko":
+- name: Korean
+- native: 한국어
+- background: 배경
+- feature: 기능
+- scenario: 시나리오
+- scenario_outline: 시나리오 개요
+- examples: 예
+- given: "*|조건<|먼저<"
+- when: "*|만일<|만약<"
+- then: "*|그러면<"
+- and: "*|그리고<"
+- but: "*|하지만<|단<"
+-"lt":
+- name: Lithuanian
+- native: lietuvių kalba
+- feature: Savybė
+- background: Kontekstas
+- scenario: Scenarijus
+- scenario_outline: Scenarijaus šablonas
+- examples: Pavyzdžiai|Scenarijai|Variantai
+- given: "*|Duota"
+- when: "*|Kai"
+- then: "*|Tada"
+- and: "*|Ir"
+- but: "*|Bet"
+-"lu":
+- name: Luxemburgish
+- native: Lëtzebuergesch
+- feature: Funktionalitéit
+- background: Hannergrond
+- scenario: Szenario
+- scenario_outline: Plang vum Szenario
+- examples: Beispiller
+- given: "*|ugeholl"
+- when: "*|wann"
+- then: "*|dann"
+- and: "*|an|a"
+- but: "*|awer|mä"
+-"lv":
+- name: Latvian
+- native: latviešu
+- feature: Funkcionalitāte|Fīča
+- background: Konteksts|Situācija
+- scenario: Scenārijs
+- scenario_outline: Scenārijs pēc parauga
+- examples: Piemēri|Paraugs
+- given: "*|Kad"
+- when: "*|Ja"
+- then: "*|Tad"
+- and: "*|Un"
+- but: "*|Bet"
+-"nl":
+- name: Dutch
+- native: Nederlands
+- feature: Functionaliteit
+- background: Achtergrond
+- scenario: Scenario
+- scenario_outline: Abstract Scenario
+- examples: Voorbeelden
+- given: "*|Gegeven|Stel"
+- when: "*|Als"
+- then: "*|Dan"
+- and: "*|En"
+- but: "*|Maar"
+-"no":
+- name: Norwegian
+- native: norsk
+- feature: Egenskap
+- background: Bakgrunn
+- scenario: Scenario
+- scenario_outline: Scenariomal|Abstrakt Scenario
+- examples: Eksempler
+- given: "*|Gitt"
+- when: "*|Når"
+- then: "*|Så"
+- and: "*|Og"
+- but: "*|Men"
+-"pl":
+- name: Polish
+- native: polski
+- feature: Właściwość
+- background: Założenia
+- scenario: Scenariusz
+- scenario_outline: Szablon scenariusza
+- examples: Przykłady
+- given: "*|Zakładając|Mając"
+- when: "*|Jeżeli|Jeśli"
+- then: "*|Wtedy"
+- and: "*|Oraz|I"
+- but: "*|Ale"
+-"pt":
+- name: Portuguese
+- native: português
+- background: Contexto
+- feature: Funcionalidade
+- scenario: Cenário|Cenario
+- scenario_outline: Esquema do Cenário|Esquema do Cenario
+- examples: Exemplos
+- given: "*|Dado|Dada|Dados|Dadas"
+- when: "*|Quando"
+- then: "*|Então|Entao"
+- and: "*|E"
+- but: "*|Mas"
+-"ro":
+- name: Romanian
+- native: română
+- background: Context
+- feature: Functionalitate|Funcționalitate|Funcţionalitate
+- scenario: Scenariu
+- scenario_outline: Structura scenariu|Structură scenariu
+- examples: Exemple
+- given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind"
+- when: "*|Cand|Când"
+- then: "*|Atunci"
+- and: "*|Si|Și|Şi"
+- but: "*|Dar"
+-"ru":
+- name: Russian
+- native: русский
+- feature: Функция|Функционал|Свойство
+- background: Предыстория|Контекст
+- scenario: Сценарий
+- scenario_outline: Структура сценария
+- examples: Примеры
+- given: "*|Допустим|Дано|Пусть"
+- when: "*|Если|Когда"
+- then: "*|То|Тогда"
+- and: "*|И|К тому же"
+- but: "*|Но|А"
+-"sv":
+- name: Swedish
+- native: Svenska
+- feature: Egenskap
+- background: Bakgrund
+- scenario: Scenario
+- scenario_outline: Abstrakt Scenario|Scenariomall
+- examples: Exempel
+- given: "*|Givet"
+- when: "*|När"
+- then: "*|Så"
+- and: "*|Och"
+- but: "*|Men"
+-"sk":
+- name: Slovak
+- native: Slovensky
+- feature: Požiadavka
+- background: Pozadie
+- scenario: Scenár
+- scenario_outline: Náčrt Scenáru
+- examples: Príklady
+- given: "*|Pokiaľ"
+- when: "*|Keď"
+- then: "*|Tak"
+- and: "*|A"
+- but: "*|Ale"
+-"sr-Latn":
+- name: Serbian (Latin)
+- native: Srpski (Latinica)
+- feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina
+- background: Kontekst|Osnova|Pozadina
+- scenario: Scenario|Primer
+- scenario_outline: Struktura scenarija|Skica|Koncept
+- examples: Primeri|Scenariji
+- given: "*|Zadato|Zadate|Zatati"
+- when: "*|Kada|Kad"
+- then: "*|Onda"
+- and: "*|I"
+- but: "*|Ali"
+-"sr-Cyrl":
+- name: Serbian
+- native: Српски
+- feature: Функционалност|Могућност|Особина
+- background: Контекст|Основа|Позадина
+- scenario: Сценарио|Пример
+- scenario_outline: Структура сценарија|Скица|Концепт
+- examples: Примери|Сценарији
+- given: "*|Задато|Задате|Задати"
+- when: "*|Када|Кад"
+- then: "*|Онда"
+- and: "*|И"
+- but: "*|Али"
+-"tr":
+- name: Turkish
+- native: Türkçe
+- feature: Özellik
+- background: Geçmiş
+- scenario: Senaryo
+- scenario_outline: Senaryo taslağı
+- examples: Örnekler
+- given: "*|Diyelim ki"
+- when: "*|Eğer ki"
+- then: "*|O zaman"
+- and: "*|Ve"
+- but: "*|Fakat|Ama"
+-"uk":
+- name: Ukrainian
+- native: Українська
+- feature: Функціонал
+- background: Передумова
+- scenario: Сценарій
+- scenario_outline: Структура сценарію
+- examples: Приклади
+- given: "*|Припустимо|Припустимо, що|Нехай|Дано"
+- when: "*|Якщо|Коли"
+- then: "*|То|Тоді"
+- and: "*|І|А також|Та"
+- but: "*|Але"
+-"uz":
+- name: Uzbek
+- native: Узбекча
+- feature: Функционал
+- background: Тарих
+- scenario: Сценарий
+- scenario_outline: Сценарий структураси
+- examples: Мисоллар
+- given: "*|Агар"
+- when: "*|Агар"
+- then: "*|Унда"
+- and: "*|Ва"
+- but: "*|Лекин|Бирок|Аммо"
+-"vi":
+- name: Vietnamese
+- native: Tiếng Việt
+- feature: Tính năng
+- background: Bối cảnh
+- scenario: Tình huống|Kịch bản
+- scenario_outline: Khung tình huống|Khung kịch bản
+- examples: Dữ liệu
+- given: "*|Biết|Cho"
+- when: "*|Khi"
+- then: "*|Thì"
+- and: "*|Và"
+- but: "*|Nhưng"
+-"zh-CN":
+- name: Chinese simplified
+- native: 简体中文
+- feature: 功能
+- background: 背景
+- scenario: 场景
+- scenario_outline: 场景大纲
+- examples: 例子
+- given: "*|假如<"
+- when: "*|当<"
+- then: "*|那么<"
+- and: "*|而且<"
+- but: "*|但是<"
+-"zh-TW":
+- name: Chinese traditional
+- native: 繁體中文
+- feature: 功能
+- background: 背景
+- scenario: 場景|劇本
+- scenario_outline: 場景大綱|劇本大綱
+- examples: 例子
+- given: "*|假設<"
+- when: "*|當<"
+- then: "*|那麼<"
+- and: "*|而且<|並且<"
+- but: "*|但是<"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
new file mode 100644
index 000000000..82903091c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
@@ -0,0 +1,155 @@
+From 86093166f066e74b61a9bc10ab3e6ea2485f06eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:22:21 +0200
+Subject: [PATCH] more.features: Perform more Gherkin v6 checks and run
+ examples.
+
+---
+ invoke.yaml | 10 ++---
+ more.features/environment.py | 28 +++++++++++++
+ .../formatter.json.validate_output.feature | 22 +++++++++-
+ more.features/run_examples.feature | 41 +++++++++++++++++++
+ 4 files changed, 93 insertions(+), 8 deletions(-)
+ create mode 100644 more.features/environment.py
+ create mode 100644 more.features/run_examples.feature
+
+diff --git a/invoke.yaml b/invoke.yaml
+index d6f141c..a32345e 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -24,13 +24,6 @@ sphinx:
+ - de
+ # PREPARED: - zh-CN
+
+-cleanup:
+- extra_directories:
+- - "build"
+- - "dist"
+- - "__WORKDIR__"
+- - reports
+-
+ cleanup:
+ extra_directories:
+ - "build"
+@@ -47,6 +40,9 @@ cleanup_all:
+ - .hypothesis
+ - .pytest_cache
+
++ extra_files:
++ - "**/testrun*.json"
++
+ behave_test:
+ scopes:
+ - features
+diff --git a/more.features/environment.py b/more.features/environment.py
+new file mode 100644
+index 0000000..9d4302b
+--- /dev/null
++++ b/more.features/environment.py
+@@ -0,0 +1,28 @@
++# -*- coding: UTF-8 -*-
++
++from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++import sys
++
++# -- MATCHES ANY TAGS: @use.with_{category}={value}
++# NOTE: active_tag_value_provider provides category values for active tags.
++python_version = "%s.%s" % sys.version_info[:2]
++active_tag_value_provider = {
++ "python.version": python_version,
++}
++active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
++
++# -----------------------------------------------------------------------------
++# HOOKS:
++# -----------------------------------------------------------------------------
++def before_all(context):
++ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
++ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++
++def before_feature(context, feature):
++ if active_tag_matcher.should_exclude_with(feature.tags):
++ feature.skip(reason=active_tag_matcher.exclude_reason)
++
++def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip(reason=active_tag_matcher.exclude_reason)
++
+diff --git a/more.features/formatter.json.validate_output.feature b/more.features/formatter.json.validate_output.feature
+index a5f8ab6..31550d5 100644
+--- a/more.features/formatter.json.validate_output.feature
++++ b/more.features/formatter.json.validate_output.feature
+@@ -34,4 +34,24 @@ Feature: Validate JSON Formatter Output
+ Then it should pass with:
+ """
+ validate: testrun3.json ... OK
+- """
+\ No newline at end of file
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave -f json -o testrun_gherkin6_1.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_1.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_1.json ... OK
++ """
++
++ @gherkin_v6
++ Scenario: Validate JSON output from example/gherkin_v6/ test run (case: partly failing)
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail -f json -o testrun_gherkin6_2.json features/"
++ When I run "../../bin/jsonschema_validate.py testrun_gherkin6_2.json"
++ Then it should pass with:
++ """
++ validate: testrun_gherkin6_2.json ... OK
++ """
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+new file mode 100644
+index 0000000..4778866
+--- /dev/null
++++ b/more.features/run_examples.feature
+@@ -0,0 +1,41 @@
++Feature: Ensure that all examples are usable
++
++ Scenario Outline: Use <example_dir>
++ Given I use the directory "<example_dir>" as working directory
++ When I run "behave <behave_cmdline>"
++ Then it should <outcome>
++
++ Examples:
++ | example_dir | behave_cmdline | outcome |
++ | examples/env_vars | features/ | pass |
++ | examples/fixture.no_background | features/ | pass |
++ | examples/gherkin_v6 | features/ | pass |
++
++
++ Scenario: examples/gherkin_v6 -- @xfail parts
++ Given I use the directory "examples/gherkin_v6" as working directory
++ When I run "behave --tags=fail features/"
++ Then it should fail with:
++ """
++ 0 features passed, 1 failed, 2 skipped
++ 0 rules passed, 1 failed, 6 skipped
++ 1 scenario passed, 2 failed, 12 skipped
++ 2 steps passed, 2 failed, 39 skipped, 0 undefined
++ """
++ And the command output should contain:
++ """
++ Failing scenarios:
++ features/rule_fails.feature:7 F0 -- Fails
++ features/rule_fails.feature:16 F2 -- Fails
++ """
++
++
++ @use.with_python.version=3.4
++ @use.with_python.version=3.5
++ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ Scenario: examples/async_step (needs: py34 or newer)
++ Given I use the directory "examples/async_step" as working directory
++ When I run "behave features/"
++ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
new file mode 100644
index 000000000..9d5dd1978
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
@@ -0,0 +1,72 @@
+From 1e3acce3b4d478400253656db354974326f9fb8d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:39:49 +0200
+Subject: [PATCH] UTIL: Correct URL and python module (old was broken, no
+ longer exists).
+
+---
+ bin/behave2cucumber_json.py | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 738e444..541061e 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -3,9 +3,10 @@
+ # CONVERT: behave JSON dialect to cucumber JSON dialect
+ # =============================================================================
+ # STATUS: __PROTOTYPE__
+-# REQUIRES: Python >= 2.6
+-# REQUIRES: https://github.com/behalfinc/b2c/
+-# SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191
++# REQUIRES: Python >= 2.7
++# REQUIRES: https://github.com/behalf-oss/behave2cucumber
++# SEE:
++# * https://github.com/behave/behave/issues/267#issuecomment-251746565
+ # =============================================================================
+ """
+ Convert a file with behave JSON data into a file with cucumber JSON data.
+@@ -17,15 +18,16 @@ import json
+ import sys
+ import os.path
+ try:
+- import b2c
++ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)")
+- print("INSTALL: pip install b2c")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
+
+ NAME = os.path.basename(__file__)
+
++
+ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+ encoding="UTF-8", pretty=True):
+ """Convert behave JSON dialect into cucumber JSON dialect.
+@@ -39,12 +41,14 @@ def convert_behave_to_cucumber_json(behave_filename, cucumber_filename,
+
+ with open(behave_filename, "r") as behave_json:
+ with open(cucumber_filename, "w+") as output_file:
+- cucumber_json = b2c.convert(json.load(behave_json, encoding))
++ behave_json = json.load(behave_json, encoding)
++ cucumber_json = behave2cucumber.convert(behave_json)
+ # cucumber_text = json.dumps(cucumber_json, **dump_kwargs)
+ # output_file.write(cucumber_text)
+ json.dump(cucumber_json, output_file, **dump_kwargs)
+ return 0
+
++
+ def main(args=None):
+ """Main function to run the script."""
+ if args is None:
+@@ -58,6 +62,7 @@ def main(args=None):
+ cucumber_filename = args[1]
+ return convert_behave_to_cucumber_json(behave_filename, cucumber_filename)
+
++
+ # -- AUTO-MAIN:
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
new file mode 100644
index 000000000..af553077f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
@@ -0,0 +1,22 @@
+From a9b7d671cfb06d49a16fc1f559b6e7e24b04ec13 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 13 Jul 2019 13:40:46 +0200
+Subject: [PATCH] UTIL: Formatting tweaks.
+
+---
+ bin/behave2cucumber_json.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave2cucumber_json.py b/bin/behave2cucumber_json.py
+index 541061e..893a5ef 100755
+--- a/bin/behave2cucumber_json.py
++++ b/bin/behave2cucumber_json.py
+@@ -20,7 +20,7 @@ import os.path
+ try:
+ import behave2cucumber
+ except ImportError:
+- print("REQUIRE: https://github.com/behalf-oss/behave2cucumber (not installed yet)")
++ print("REQUIRE: https://github.com/behalf-oss/behave2cucumber")
+ print("INSTALL: pip install behave2cucumber")
+ sys.exit(2)
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
new file mode 100644
index 000000000..1a0c1d6f7
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
@@ -0,0 +1,23 @@
+From 26e15e7a1bb5b33db38eb0c77730162dbb6a05d8 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:21:36 -0500
+Subject: [PATCH] Fixed a bug where use_fixture_by_tag didn't return the actual
+ fixture if the registry entry was just a direct function.
+
+---
+ behave/fixture.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/behave/fixture.py b/behave/fixture.py
+index 3a9f1bc..51e18bf 100644
+--- a/behave/fixture.py
++++ b/behave/fixture.py
+@@ -272,7 +272,7 @@ def use_fixture_by_tag(tag, context, fixture_registry):
+
+ if callable(fixture_data):
+ fixture_func = fixture_data
+- use_fixture(fixture_func, context)
++ return use_fixture(fixture_func, context)
+ elif isinstance(fixture_data, (tuple, list)):
+ assert len(fixture_data) == 3
+ fixture_func, fixture_args, fixture_kwargs = fixture_data
diff --git a/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
new file mode 100644
index 000000000..be817883d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
@@ -0,0 +1,62 @@
+From 70e538f77e88bef881b33fd50ea00e9a309acdc4 Mon Sep 17 00:00:00 2001
+From: Jon-Pierre Gentil <jgentil@sebistar.net>
+Date: Thu, 1 Aug 2019 11:48:08 -0500
+Subject: [PATCH] Added issue unit test
+
+---
+ tests/issues/test_issue0767.py | 46 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 46 insertions(+)
+ create mode 100644 tests/issues/test_issue0767.py
+
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+new file mode 100644
+index 0000000..1de3589
+--- /dev/null
++++ b/tests/issues/test_issue0767.py
+@@ -0,0 +1,46 @@
++"""
++https://github.com/behave/behave/issues/767
++
++When trying to do something like::
++
++ fixture_registry = {'fixture.foo': foo_fixture}
++ f = use_fixture_by_tag('fixture.foo', context, fixture_registry)
++
++Behave returns nothing. ::
++
++ repr(f)
++ 'None'
++
++This seems to be an oversight.
++"""
++
++from mock import Mock
++
++def test_issue_767_use_feature_by_tag_has_no_return():
++ """Verifies that issue #767 is fixed."""
++ from behave.fixture import fixture, use_fixture_by_tag
++ from behave.runner import Context
++
++ @fixture(name='fixture.foo')
++ def foo_fixture(context, *args, **kwargs):
++ context.foo = 'foo'
++ return context.foo
++
++ # -- SCHEMA 1: fixture_func
++ fixture_registry1 = {
++ "fixture.foo": foo_fixture
++ }
++ # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
++ fixture_registry2 = {
++ "fixture.foo": (foo_fixture, (), {})
++ }
++
++ context = Context(runner=Mock())
++ f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
++ assert f1 == 'foo'
++ assert context.foo is f1
++
++ context = Context(runner=Mock())
++ f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
++ assert f2 == 'foo'
++ assert context.foo is f2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
new file mode 100644
index 000000000..ac31b2d41
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
@@ -0,0 +1,60 @@
+From 8b81a404d50bfa4a05ec93c0a8220927a5593d6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:14:59 +0200
+Subject: [PATCH] Merge pull-request #767 with minor tweaks. FIX:
+ use_fixture_by_tag didn't return the actual fixture in all cases.
+
+---
+ CHANGES.rst | 1 +
+ tests/issues/test_issue0767.py | 17 +++++++++--------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 15a4ef9..7a9163f 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -43,6 +43,7 @@ FIXED:
+
+ MINOR:
+
++* pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+ * pull #655: Use pytest instead of py.test per upstream recommendation (provided by: scop)
+diff --git a/tests/issues/test_issue0767.py b/tests/issues/test_issue0767.py
+index 1de3589..401dbd0 100644
+--- a/tests/issues/test_issue0767.py
++++ b/tests/issues/test_issue0767.py
+@@ -15,11 +15,12 @@ This seems to be an oversight.
+ """
+
+ from mock import Mock
++from behave.fixture import fixture, use_fixture_by_tag
++from behave.runner import Context
++
+
+ def test_issue_767_use_feature_by_tag_has_no_return():
+ """Verifies that issue #767 is fixed."""
+- from behave.fixture import fixture, use_fixture_by_tag
+- from behave.runner import Context
+
+ @fixture(name='fixture.foo')
+ def foo_fixture(context, *args, **kwargs):
+@@ -36,11 +37,11 @@ def test_issue_767_use_feature_by_tag_has_no_return():
+ }
+
+ context = Context(runner=Mock())
+- f1 = use_fixture_by_tag('fixture.foo', context, fixture_registry1)
+- assert f1 == 'foo'
+- assert context.foo is f1
++ fixture1 = use_fixture_by_tag("fixture.foo", context, fixture_registry1)
++ assert fixture1 == "foo"
++ assert context.foo is fixture1
+
+ context = Context(runner=Mock())
+- f2 = use_fixture_by_tag('fixture.foo', context, fixture_registry2)
+- assert f2 == 'foo'
+- assert context.foo is f2
++ fixture2 = use_fixture_by_tag("fixture.foo", context, fixture_registry2)
++ assert fixture2 == "foo"
++ assert context.foo is fixture2
diff --git a/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
new file mode 100644
index 000000000..874c95b69
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
@@ -0,0 +1,83 @@
+From 5d4478bc85f3a12efe0c85638665e23a0ab233d1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 7 Aug 2019 23:55:05 +0200
+Subject: [PATCH] CHECK: Issue #766 -- PrettyFormatter: UnicodeError
+
+---
+ issue.features/issue0766.feature | 47 +++++++++++++++++++++++++
+ issue.features/steps/issue0766_steps.py | 12 +++++++
+ 2 files changed, 59 insertions(+)
+ create mode 100644 issue.features/issue0766.feature
+ create mode 100644 issue.features/steps/issue0766_steps.py
+
+diff --git a/issue.features/issue0766.feature b/issue.features/issue0766.feature
+new file mode 100644
+index 0000000..94d44d8
+--- /dev/null
++++ b/issue.features/issue0766.feature
+@@ -0,0 +1,47 @@
++@issue
++@not_reproducible
++Feature: Issue #766 -- UnicodeEncodeError in PrettyFormatter
++
++ Explore the described problem.
++
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario:
++ Given a step with table data:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++
++ Scenario: Explore problem by using the pretty formatter
++ Given a new working directory
++ And a file named "features/syndrome_766.feature" with:
++ """
++ Feature: Alice
++ Scenario Outline:
++ Given a step with name="<name>"
++
++ Examples:
++ | name | value | comment |
++ | 😄 | 123 | Use emoticon (smiley) in a table |
++ """
++ And a file named "features/steps/issue766_steps.py" with:
++ """
++ from behave import given
++
++ @given(u'a step with name="{name}"')
++ def step_with_table_data(ctx, name):
++ pass
++ """
++ When I run "behave -f pretty features/syndrome_766.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ 1 step passed, 0 failed, 0 skipped, 0 undefine
++ """
++ And the command output should not contain "UnicodeEncodeError"
++ And the command output should not contain "Traceback"
+diff --git a/issue.features/steps/issue0766_steps.py b/issue.features/steps/issue0766_steps.py
+new file mode 100644
+index 0000000..33ed317
+--- /dev/null
++++ b/issue.features/steps/issue0766_steps.py
+@@ -0,0 +1,12 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import print_function
++from behave import given
++
++@given(u'a step with table data')
++def step_with_table_data(ctx):
++ assert ctx.table is not None, "REQUIRE: step.table"
++
++@given(u'a step with name="{name}"')
++def step_with_table_data(ctx, name):
++ print(u"name: {}".format(name))
diff --git a/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..6f5a1dad9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,74 @@
+From 507ce8af0c2513893008904db3a7dbd9de5ac2d4 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:08:22 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ behave/model.py | 8 +++++++-
+ issue.features/issue0772.feature | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 1 deletion(-)
+ create mode 100644 issue.features/issue0772.feature
+
+diff --git a/behave/model.py b/behave/model.py
+index 69f38ab..f46d2c2 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -10,7 +10,7 @@ This module provides the model element class that represent a behave model:
+ * ...
+ """
+
+-from __future__ import absolute_import, with_statement
++from __future__ import absolute_import, with_statement, print_function
+ import copy
+ import difflib
+ import logging
+@@ -1355,6 +1355,12 @@ class ScenarioOutlineBuilder(object):
+ example.index = example_index+1
+ params["examples.name"] = example.name
+ params["examples.index"] = _text(example.index)
++ if not example.table:
++ # -- SYNDROME: Examples keyword without table
++ print("ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome ({0})"\
++ .format(example.location))
++ continue
++
+ for row_index, row in enumerate(example.table):
+ row.index = row_index+1
+ row.id = "%d.%d" % (example.index, row.index)
+diff --git a/issue.features/issue0772.feature b/issue.features/issue0772.feature
+new file mode 100644
+index 0000000..eba0ea5
+--- /dev/null
++++ b/issue.features/issue0772.feature
+@@ -0,0 +1,31 @@
++@issue
++Feature: Issue #772 -- Syndrome: ScenarioOutline with Examples keyword w/o Table
++
++
++
++ Background: Setup
++ Given a new working directory
++ And a file named "features/syndrome_772.feature" with:
++ """
++ Feature: Examples without table
++
++ Scenario Outline:
++ Given a step passes
++ When another step passes
++
++ Examples: Without table
++ """
++ And a file named "features/steps/use_step_library.py" with:
++ """
++ # -- REUSE STEPS:
++ import behave4cmd0.passing_steps
++ """
++
++ Scenario: Use ScenarioOutline with Examples keyword without table
++ When I run "behave -f plain features/syndrome_772.feature"
++ Then it should pass with:
++ """
++ Feature: Examples without table
++ ERROR: ScenarioOutline.Examples: Has NO-TABLE syndrome (features/syndrome_772.feature:7)
++ """
++ And the command output should not contain "Parser failure in state"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
new file mode 100644
index 000000000..867e16b22
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
@@ -0,0 +1,21 @@
+From 0ecb55eed2f0d4b7aa49cfd50f9d8ab34d70628a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 22 Sep 2019 18:09:50 +0200
+Subject: [PATCH] FIX issue #772: ScenarioOutline.Examples without table.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 7a9163f..ba4daad 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -33,6 +33,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
new file mode 100644
index 000000000..93e70d3d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
@@ -0,0 +1,21 @@
+From 5b60bba6b115758155c97eb8aea22c28c407fd6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:23:08 +0100
+Subject: [PATCH] Nibble TravisCI to wake up.
+
+---
+ .travis.yml | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index c6027e0..781a610 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -6,6 +6,7 @@ python:
+ - "3.7"
+ - "2.7"
+
++
+ # -- DISABLE-TEMPORARILY: Ensure faster builds
+ # - "3.6"
+ # - "3.5"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
new file mode 100644
index 000000000..f492b8a93
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
@@ -0,0 +1,37 @@
+From a7a1b60b3a723af51515ba64170ae99695755df2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:30:11 +0100
+Subject: [PATCH] Tweak pytest version selection
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ setup.py | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 73d65f6..5f31802 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,6 +1,7 @@
+ mock
+ PyHamcrest >= 1.9
+-pytest >= 3.0
++pytest < 5.0; python_version < '3.0'
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+
+ # -- NEEDED: By some tests (as proof of concept)
+diff --git a/setup.py b/setup.py
+index 8de3ec0..75d6847 100644
+--- a/setup.py
++++ b/setup.py
+@@ -87,7 +87,8 @@ setup(
+ "colorama",
+ ],
+ tests_require=[
+- "pytest >= 4.2",
++ "pytest < 5.0; python_version < '3.0'", # >= 4.2
++ "pytest >= 5.0; python_version >= '3.0'",
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
new file mode 100644
index 000000000..9f1a17fb3
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
@@ -0,0 +1,37 @@
+From ab69c9eacffd387dd756425030d287cd182df12f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 11 Dec 2019 08:37:11 +0100
+Subject: [PATCH] Tweak pytest configuration to silence JUnit XML dialect
+ warning.
+
+---
+ pytest.ini | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index ff2a8a2..228279c 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,9 +16,10 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
++minversion = 4.2
+ testpaths = tests
+ python_files = test_*.py
++junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+@@ -27,6 +28,10 @@ markers =
+ smoke
+ slow
+
++# -- PREPARED:
++# filterwarnings =
++# ignore:.*invalid escape sequence.*:DeprecationWarning
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
new file mode 100644
index 000000000..702b73023
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
@@ -0,0 +1,56 @@
+From 3883099e542901f457835900d42a60ea671713ea Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 15:50:37 +0100
+Subject: [PATCH] Add basic feature-test for wildcard pattern-matching that is
+ supported by cucumber-tag-expressions (python-only).
+
+---
+ .../tags.tag_expression_v2.wildcards.feature | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+ create mode 100644 features/tags.tag_expression_v2.wildcards.feature
+
+diff --git a/features/tags.tag_expression_v2.wildcards.feature b/features/tags.tag_expression_v2.wildcards.feature
+new file mode 100644
+index 0000000..372a731
+--- /dev/null
++++ b/features/tags.tag_expression_v2.wildcards.feature
+@@ -0,0 +1,39 @@
++Feature: Tag Expression v2 Extension: Wildcards for tag matching
++
++ As a tester
++ I want to use a wildcard pattern to select tags following a naming scheme
++ So that it is simpler to select a subset of scenarios and features.
++
++ . SPECIFICATION: Wildcards in tag-expressions v2
++ . * Use file-name matching wildcards (fnmatch): *, ?
++ . * a tag expression is a boolean expression
++ . * a tag expression supports the operators: and, or, not
++ . * a tag expression supports '(' and ')' for grouping expressions
++ .
++ . EXAMPLES:
++ . | Tag expression | Comment |
++ . | @foo.* | Matches any tags that start with "@foo." |
++ . | not @foo.* | Excludes any element that have tags that start with "@foo." |
++
++
++ Scenario: Select tags that match the "@foo.*" pattern
++ Given the tag expression "@foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | no |
++ | @foo | no |
++ | @foo.one | yes |
++ | @foo.two | yes |
++ | @other | no |
++ | @foo.3 @other | yes |
++
++ Scenario: Select tags that do not match the "@foo.*" pattern
++ Given the tag expression "not @foo.*"
++ Then the tag expression selects elements with tags:
++ | tags | selected? |
++ | | yes |
++ | @foo | yes |
++ | @foo.one | no |
++ | @foo.two | no |
++ | @other | yes |
++ | @foo.3 @other | no |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
new file mode 100644
index 000000000..098f6ae99
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
@@ -0,0 +1,141 @@
+From 1f318bee1273d6fa245eee348c72bdebfe28a703 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:12:14 +0100
+Subject: [PATCH] UPDATE: dependencies (path.py <=> path, pytest, ...)
+
+---
+ py.requirements/ci.tox.txt | 8 ++++++--
+ py.requirements/ci.travis.txt | 11 ++++++++---
+ py.requirements/develop.txt | 5 ++++-
+ py.requirements/testing.txt | 7 +++++--
+ setup.py | 6 ++++--
+ tasks/py.requirements.txt | 5 ++++-
+ 6 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 6b3b3ae..4001bc2 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -2,8 +2,12 @@
+ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
+ # ============================================================================
+
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+-path.py >= 10.1
++
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 5f31802..de4120a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,12 +1,17 @@
+-mock
+-PyHamcrest >= 1.9
++# ============================================================================
++# PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
++# ============================================================================
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a16d7bf..a92ee5f 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -6,10 +6,13 @@
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- CONFIGURATION MANAGEMENT (helpers):
+ # FORMER: bumpversion >= 0.4.0
+ bump2version >= 0.5.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index a418739..85b0908 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,11 +4,14 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 4.2
++pytest < 5.0; python_version < '3.0' # pytest >= 4.2
++pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 11.5.0
++# HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
+diff --git a/setup.py b/setup.py
+index 75d6847..2afc147 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.8.2",
++ "parse >= 1.9.1",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+@@ -92,7 +92,9 @@ setup(
+ "pytest-html >= 1.19.0",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.9",
+- "path.py >= 11.5.0"
++ # -- HINT: path.py => path (python-install-package was renamed for python3)
++ "path.py >= 11.5.0; python_version < '3.5'",
++ "path >= 13.1.0; python_version >= '3.5'",
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index a77d3bc..810f834 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -9,10 +9,13 @@
+ # ============================================================================
+
+ invoke >= 1.2.0
+-path.py >= 11.5.0
+ pycmd
+ six >= 1.12.0
+
++# -- HINT: path.py => path (python-install-package was renamed for python3)
++path.py >= 11.5.0; python_version < '3.5'
++path >= 13.1.0; python_version >= '3.5'
++
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+ backports.shutil_which; python_version <= '3.3'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
new file mode 100644
index 000000000..d0e9cb005
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
@@ -0,0 +1,25 @@
+From f18ada8b98ff62c47d51bc38e5654e7c363d34f7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 20:16:43 +0100
+Subject: [PATCH] pytest: Disable DeprecatedWarning from distutils package.
+
+---
+ pytest.ini | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/pytest.ini b/pytest.ini
+index 228279c..b9f281a 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -29,8 +29,9 @@ markers =
+ slow
+
+ # -- PREPARED:
+-# filterwarnings =
+-# ignore:.*invalid escape sequence.*:DeprecationWarning
++filterwarnings =
++ ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
++# ignore:.*invalid escape sequence.*:DeprecationWarning
+
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+ # norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
diff --git a/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
new file mode 100644
index 000000000..1709b9abc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
@@ -0,0 +1,216 @@
+From a2a8db7c0a5c9e7a6a43abe9b7c5d9474c13e75d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:20:18 +0100
+Subject: [PATCH] CLEANUP: Add ContextMode enum related to #797
+
+Add ContextMode enum to cleanup weirdness related to issue #797.
+Pre-existing Context.BEHAVE/USER constants overshadowed user attributes
+in Context.attribute retrieval case.
+---
+ behave/runner.py | 33 ++++++++++++++++++++++-----------
+ tests/unit/test_runner.py | 29 +++++++++++++++--------------
+ 2 files changed, 37 insertions(+), 25 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index cbedb5a..bcf4ab2 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -21,6 +21,7 @@ from behave.runner_util import \
+ collect_feature_locations, parse_features, \
+ exec_file, load_step_modules, PathManager
+ from behave.step_registry import registry as the_step_registry
++from enum import Enum
+
+ if six.PY2:
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
+@@ -45,6 +46,16 @@ class ContextMaskWarning(UserWarning):
+ pass
+
+
++class ContextMode(Enum):
++ """Used to distinguish between the two usage modes while using the context:
++
++ * BEHAVE: Indicates "behave" (internal) mode
++ * USER: Indicates "user" mode (in steps, hooks, fixtures, ...)
++ """
++ BEHAVE = 1
++ USER = 2
++
++
+ class Context(object):
+ """Hold contextual information during the running of tests.
+
+@@ -147,8 +158,8 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- BEHAVE = "behave"
+- USER = "user"
++ # BEHAVE = "behave"
++ # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -166,7 +177,7 @@ class Context(object):
+ self._stack = [d]
+ self._record = {}
+ self._origin = {}
+- self._mode = self.BEHAVE
++ self._mode = ContextMode.BEHAVE
+
+ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+@@ -260,11 +271,11 @@ class Context(object):
+
+ def _use_with_behave_mode(self):
+ """Provides a context manager for using the context in BEHAVE mode."""
+- return use_context_with_mode(self, Context.BEHAVE)
++ return use_context_with_mode(self, ContextMode.BEHAVE)
+
+ def use_with_user_mode(self):
+ """Provides a context manager for using the context in USER mode."""
+- return use_context_with_mode(self, Context.USER)
++ return use_context_with_mode(self, ContextMode.USER)
+
+ def user_mode(self):
+ warnings.warn("Use 'use_with_user_mode()' instead",
+@@ -291,11 +302,11 @@ class Context(object):
+
+ def _emit_warning(self, attr, params):
+ msg = ""
+- if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE:
++ if self._mode is ContextMode.BEHAVE and self._origin[attr] is not ContextMode.BEHAVE:
+ msg = "behave runner is masking context attribute '%(attr)s' " \
+ "originally set in %(function)s (%(filename)s:%(line)s)"
+- elif self._mode is self.USER:
+- if self._origin[attr] is not self.USER:
++ elif self._mode is ContextMode.USER:
++ if self._origin[attr] is not ContextMode.USER:
+ msg = "user code is masking context attribute '%(attr)s' " \
+ "originally set by behave"
+ elif self._config.verbose:
+@@ -442,13 +453,13 @@ class Context(object):
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+- """Switch context to BEHAVE or USER mode.
++ """Switch context to ContextMode.BEHAVE or ContextMode.USER mode.
+ Provides a context manager for switching between the two context modes.
+
+ .. sourcecode:: python
+
+ context = Context()
+- with use_context_with_mode(context, Context.BEHAVE):
++ with use_context_with_mode(context, ContextMode.BEHAVE):
+ ... # Do something
+ # -- POSTCONDITION: Original context._mode is restored.
+
+@@ -456,7 +467,7 @@ def use_context_with_mode(context, mode):
+ :param mode: Mode to apply to context object.
+ """
+ # pylint: disable=protected-access
+- assert mode in (Context.BEHAVE, Context.USER)
++ assert mode in (ContextMode.BEHAVE, ContextMode.USER)
+ current_mode = context._mode
+ try:
+ context._mode = mode
+diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py
+index f0d03cd..beaff8f 100644
+--- a/tests/unit/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -17,6 +17,7 @@ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+ from behave import parser, runner
++from behave.runner import ContextMode
+ from behave.exception import ConfigError
+ from behave.formatter.base import StreamOpener
+
+@@ -36,29 +37,29 @@ class TestContext(unittest.TestCase):
+
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ assert False, "XFAIL"
+ except AssertionError:
+ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -66,21 +67,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.BEHAVE
++ initial_mode = ContextMode.BEHAVE
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- assert self.context._mode == runner.Context.USER
++ assert self.context._mode == ContextMode.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
+@@ -88,21 +89,21 @@ class TestContext(unittest.TestCase):
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ self.context.thing = "stuff"
+ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+ # pylint: disable=protected-access
+- initial_mode = runner.Context.USER
++ initial_mode = ContextMode.USER
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- assert self.context._mode == runner.Context.BEHAVE
++ assert self.context._mode == ContextMode.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+ assert self.context._mode == initial_mode
--git a/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
new file mode 100644
index 000000000..2ea9fa8e9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
@@ -0,0 +1,22 @@
+From bceaaac964834add6016c6e7fb62ac512da99072 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:25:00 +0100
+Subject: [PATCH] Cleanup comments
+
+---
+ behave/runner.py | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index bcf4ab2..6b20937 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -158,8 +158,6 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
+- # BEHAVE = "behave"
+- # USER = "user"
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
new file mode 100644
index 000000000..fc2b8eae4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
@@ -0,0 +1,29 @@
+From 7c8f5778ae1f325861d4cdbd410c553829ca08ef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:51:47 +0100
+Subject: [PATCH] FIX: sphinx-build problem: async_steps3x.py
+
+---
+ docs/new_and_noteworthy_v1.2.6.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/new_and_noteworthy_v1.2.6.rst b/docs/new_and_noteworthy_v1.2.6.rst
+index 848c409..2c8e865 100644
+--- a/docs/new_and_noteworthy_v1.2.6.rst
++++ b/docs/new_and_noteworthy_v1.2.6.rst
+@@ -325,13 +325,13 @@ A simple example for the implementation of the async-steps is shown for:
+ * Python 3.5 with new ``async``/``await`` keywords
+ * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps35.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps35.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps35.py
+
+
+-.. literalinclude:: ../examples/async_step/features/steps/async_steps34.py
++.. literalinclude:: ../examples/async_step/features/steps/_async_steps34.py
+ :language: python
+ :prepend:
+ # -- FILE: features/steps/async_steps34.py
diff --git a/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
new file mode 100644
index 000000000..588ef50cd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
@@ -0,0 +1,185 @@
+From 041322c92c07199822fe4faf488b3afc6bec30da Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 21:52:53 +0100
+Subject: [PATCH] docs: Rename page 'parse_expressions' (was:
+ parse_builtin_types) and update table to current parse-1.12.1
+
+---
+ docs/appendix.rst | 2 +-
+ docs/parse_builtin_types.rst | 59 ------------------------
+ docs/parse_expressions.rst | 87 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 88 insertions(+), 60 deletions(-)
+ delete mode 100644 docs/parse_builtin_types.rst
+ create mode 100644 docs/parse_expressions.rst
+
+diff --git a/docs/appendix.rst b/docs/appendix.rst
+index 8c0cb05..79b5455 100644
+--- a/docs/appendix.rst
++++ b/docs/appendix.rst
+@@ -11,7 +11,7 @@ Appendix
+
+ formatters
+ context_attributes
+- parse_builtin_types
++ parse_expressions
+ regular_expressions
+ test_domains
+ behave_ecosystem
+diff --git a/docs/parse_builtin_types.rst b/docs/parse_builtin_types.rst
+deleted file mode 100644
+index 32e18ec..0000000
+--- a/docs/parse_builtin_types.rst
++++ /dev/null
+@@ -1,59 +0,0 @@
+-.. _id.appendix.parse_builtin_types:
+-
+-Predefined Data Types in ``parse``
+-==============================================================================
+-
+-:pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_)
+-under the hoods to parse parameters in step definitions.
+-This leads to rather simple and readable parse expressions for step parameters.
+-
+-.. code-block:: python
+-
+- # -- FILE: features/steps/type_transform_example_steps.py
+- from behave import given
+-
+- @given('I have {number:d} friends') #< Convert 'number' into int type.
+- def step_given_i_have_number_friends(context, number):
+- assert number > 0
+- ...
+-
+-Therefore, the following ``parse types`` are already supported
+-in step definitions without registration of any *user-defined type*:
+-
+-
+-===== =========================================== ============
+-Type Characters Matched Output Type
+-===== =========================================== ============
+- w Letters and underscore str
+- W Non-letter and underscore str
+- s Whitespace str
+- S Non-whitespace str
+- d Digits (effectively integer numbers) int
+- D Non-digit str
+- n Numbers with thousands separators (, or .) int
+- % Percentage (converted to value/100.0) float
+- f Fixed-point numbers float
+- e Floating-point numbers with exponent float
+- e.g. 1.1e-10, NAN (all case insensitive)
+- g General number format (either d, f or e) float
+- b Binary numbers int
+- o Octal numbers int
+- x Hexadecimal numbers (lower and upper case) int
+- ti ISO 8601 format date/time datetime
+- e.g. 1972-01-20T10:21:36Z
+- te RFC2822 e-mail format date/time datetime
+- e.g. Mon, 20 Jan 1972 10:21:36 +1000
+- tg Global (day/month) format date/time datetime
+- e.g. 20/1/1972 10:21:36 AM +1:00
+- ta US (month/day) format date/time datetime
+- e.g. 1/20/1972 10:21:36 PM +10:30
+- tc ctime() format date/time datetime
+- e.g. Sun Sep 16 01:03:52 1973
+- th HTTP log format date/time datetime
+- e.g. 21/Nov/2011:00:07:11 +0000
+- tt Time time
+- e.g. 10:21:36 PM -5:30
+-===== =========================================== ============
+-
+-
+-.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+new file mode 100644
+index 0000000..36ca549
+--- /dev/null
++++ b/docs/parse_expressions.rst
+@@ -0,0 +1,87 @@
++.. _id.appendix.parse_expressions:
++
++==============================================================================
++Parse Expressions
++==============================================================================
++
++.. index:: parse expressions, regexp
++
++`Parse expressions`_ are a simplified form of regular expressions.
++The actual regular expression is hidden behind the **type** name / hint.
++
++`Parse expressions`_ are used in step definitions as a simplified alternative
++to regular expressions. They are used for parameters and type conversions
++(which are not supported for regular expression patterns).
++
++.. code-block:: python
++
++ # -- FILE: features/steps/example_steps.py
++ from behave import when
++
++ @when('we implement {number:d} tests')
++ def step_impl(context, number): # -- NOTE: number is converted into integer
++ assert number > 1 or number == 0
++ context.tests_count = number
++
++The following tables provide a overview of the `parse expressions`_ syntax.
++See also `Python regular expressions`_ description in the Python `re module`_.
++
++===== =========================================== ========
++Type Characters Matched Output
++===== =========================================== ========
++l Letters (ASCII) str
++w Letters, numbers and underscore str
++W Not letters, numbers and underscore str
++s Whitespace str
++S Non-whitespace str
++d Digits (effectively integer numbers) int
++D Non-digit str
++n Numbers with thousands separators (, or .) int
++% Percentage (converted to value/100.0) float
++f Fixed-point numbers float
++F Decimal numbers Decimal
++e Floating-point numbers with exponent float
++ e.g. 1.1e-10, NAN (all case insensitive)
++g General number format (either d, f or e) float
++b Binary numbers int
++o Octal numbers int
++x Hexadecimal numbers (lower and upper case) int
++ti ISO 8601 format date/time datetime
++ e.g. 1972-01-20T10:21:36Z ("T" and "Z"
++ optional)
++te RFC2822 e-mail format date/time datetime
++ e.g. Mon, 20 Jan 1972 10:21:36 +1000
++tg Global (day/month) format date/time datetime
++ e.g. 20/1/1972 10:21:36 AM +1:00
++ta US (month/day) format date/time datetime
++ e.g. 1/20/1972 10:21:36 PM +10:30
++tc ctime() format date/time datetime
++ e.g. Sun Sep 16 01:03:52 1973
++th HTTP log format date/time datetime
++ e.g. 21/Nov/2011:00:07:11 +0000
++ts Linux system log format date/time datetime
++ e.g. Nov 9 03:37:44
++tt Time time
++ e.g. 10:21:36 PM -5:30
++===== =========================================== ========
++
++
++===================== ==============================================================
++Cardinality Description
++===================== ==============================================================
++``?`` Pattern with cardinality 0..1: optional part (question mark).
++``*`` Pattern with cardinality zero or more, 0.. (asterisk).
++``+`` Pattern with cardinality one or more, 1.. (plus sign).
++``{m}`` Matches ``m`` repetitions of a pattern.
++``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
++``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
++===================== ==============================================================
++
++
++.. _parse module: https://github.com/r1chardj0n3s/parse
++.. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++
++.. _re module: https://docs.python.org/3/library/re.html#module-re
++.. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
++.. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
new file mode 100644
index 000000000..e53b10c92
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
@@ -0,0 +1,40 @@
+From b04042837ff57000709adc716f711b29ccc59584 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 15 Dec 2019 22:08:05 +0100
+Subject: [PATCH] docs: parse_expression, add links to parse_type module and
+ CardinalityField support.
+
+---
+ docs/parse_expressions.rst | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/docs/parse_expressions.rst b/docs/parse_expressions.rst
+index 36ca549..3810222 100644
+--- a/docs/parse_expressions.rst
++++ b/docs/parse_expressions.rst
+@@ -65,6 +65,8 @@ tt Time time
+ e.g. 10:21:36 PM -5:30
+ ===== =========================================== ========
+
++If `parse_type`_ module is used, the cardinality of a type can be specified, too
++(by using the `CardinalityField`_ support):
+
+ ===================== ==============================================================
+ Cardinality Description
+@@ -72,14 +74,13 @@ Cardinality Description
+ ``?`` Pattern with cardinality 0..1: optional part (question mark).
+ ``*`` Pattern with cardinality zero or more, 0.. (asterisk).
+ ``+`` Pattern with cardinality one or more, 1.. (plus sign).
+-``{m}`` Matches ``m`` repetitions of a pattern.
+-``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern.
+-``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters.
+ ===================== ==============================================================
+
+
+ .. _parse module: https://github.com/r1chardj0n3s/parse
++.. _parse_type: https://github.com/jenisys/parse_type
+ .. _string.format: https://docs.python.org/3/library/string.html#format-string-syntax
++.. _CardinalityField: https://github.com/jenisys/parse_type/blob/master/README.rst#extended-parser-with-cardinalityfield-support
+
+ .. _re module: https://docs.python.org/3/library/re.html#module-re
+ .. _Python regular expressions: https://docs.python.org/3/library/re.html#module-re
diff --git a/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
new file mode 100644
index 000000000..b92043f6f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
@@ -0,0 +1,65 @@
+From 18ff55874640f54652d13874bec5b67f6be12bef Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 19 Dec 2019 12:31:47 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev2 (was: 1.2.7.dev1)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/version.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index a5d3d2f..4f2bb76 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev1
++current_version = 1.2.7.dev2
+ files = behave/version.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index c0ef36b..c4e75f6 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev1
++1.2.7.dev2
+diff --git a/behave/version.py b/behave/version.py
+index b19cb5e..67f4a41 100644
+--- a/behave/version.py
++++ b/behave/version.py
+@@ -1,2 +1,2 @@
+ # -- BEHAVE-VERSION:
+-VERSION = "1.2.7.dev1"
++VERSION = "1.2.7.dev2"
+diff --git a/pytest.ini b/pytest.ini
+index b9f281a..df2a81f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -21,7 +21,7 @@ testpaths = tests
+ python_files = test_*.py
+ junit_family = xunit2
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev1
++ --metadata PACKAGE_VERSION 1.2.7.dev2
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index 2afc147..23f6654 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev1",
++ version="1.2.7.dev2",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
new file mode 100644
index 000000000..689138b42
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
@@ -0,0 +1,223 @@
+From 878f1b58cb0ef3081279cb1cb5673250b3665d6c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 20 Dec 2019 16:45:46 +0100
+Subject: [PATCH] Gherkin parser: Cleanups related to question in #800
+ (ParseError usage)
+
+---
+ CHANGES.rst | 1 +
+ behave/parser.py | 41 +++++++++++++-------
+ features/background.feature | 4 +-
+ features/parser.background.sad_cases.feature | 10 ++---
+ features/parser.feature.sad_cases.feature | 6 +--
+ issue.features/issue0148.feature | 2 +-
+ 6 files changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ba4daad..5653492 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -44,6 +44,7 @@ FIXED:
+
+ MINOR:
+
++* issue #800: Cleanups related to Gherkin parser/ParseError question (submitted by: otstanteplz)
+ * pull #767: FIX: use_fixture_by_tag didn't return the actual fixture in all cases (provided by: jgentil)
+ * pull #751: gherkin: Adding Rule keyword translation in portuguese and spanish to gherkin-languages.json (provided by: dunossauro)
+ * pull #660: Fix minor typos (provided by: rrueth)
+diff --git a/behave/parser.py b/behave/parser.py
+index 520f678..58c68be 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -132,13 +132,24 @@ def parse_tags(text):
+
+
+ class ParserError(Exception):
+- def __init__(self, message, line, filename=None, line_text=None):
+- if line:
+- message += u" at line %d" % line
+- if line_text:
+- message += u': "%s"' % line_text.strip()
++ @staticmethod
++ def make_annotated(message, line_number, line_text=None, reason=None):
++ """Make annotated message enriched w/ line_number, line_text."""
++ if line_number:
++ message += u" at line %d" % line_number
++ if line_text:
++ message += u': "%s"' % line_text.strip()
++ if reason:
++ message += u"\nREASON: %s" % reason
++ return message
++
++ def __init__(self, message, line, filename=None, line_text=None,
++ reason=None, use_annotated_message=True):
++ if use_annotated_message:
++ message = self.make_annotated(message, line, line_text, reason)
++
+ super(ParserError, self).__init__(message)
+- self.line = line
++ self.line = line # Line number of parse failure.
+ self.line_text = line_text
+ self.filename = filename
+
+@@ -386,14 +397,13 @@ class Parser(object):
+ line = line.strip()
+ msg = u"Parser in unknown state %s;" % self.state
+ raise ParserError(msg, self.line, self.filename, line)
++
+ if not func(line):
+ line = line.strip()
+- msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \
+- (self.state, self.line, line)
++ msg = u'\nParser failure in state=%s' % self.state
+ reason = self.ask_parse_failure_oracle(line)
+- if reason:
+- msg += u"REASON: %s" % reason
+- raise ParserError(msg, None, self.filename)
++ raise ParserError(msg, self.line, self.filename,
++ line_text=line, reason=reason)
+
+ def action_init(self, line):
+ line = line.strip()
+@@ -642,7 +652,7 @@ class Parser(object):
+ self.table = model.Table(cells, self.line)
+ else:
+ if len(cells) != len(self.table.headings):
+- raise ParserError(u"Malformed table", self.line)
++ raise ParserError(u"Malformed table", self.line, self.filename)
+ # MAYBE: self.filename)
+ self.table.add_row(cells, self.line)
+ return True
+@@ -704,8 +714,8 @@ class Parser(object):
+ break # -- COMMENT: Skip rest of line.
+ else:
+ # -- BAD-TAG: Abort here.
+- raise ParserError(u"tag: %s (line: %s)" % (word, line),
+- self.line, self.filename)
++ message = u"tag: %s (line: %s)" % (word, line)
++ raise ParserError(message, self.line, self.filename)
+ return tags
+
+ def parse_step(self, line):
+@@ -723,7 +733,8 @@ class Parser(object):
+ step_text_after_keyword = line[len(kw):].strip()
+ if step_type in ("and", "but"):
+ if not self.last_step:
+- raise ParserError(u"No previous step", self.line)
++ raise ParserError(u"No previous step",
++ self.line, self.filename)
+ step_type = self.last_step
+ else:
+ self.last_step = step_type
+diff --git a/features/background.feature b/features/background.feature
+index b2f5833..65c4882 100644
+--- a/features/background.feature
++++ b/features/background.feature
+@@ -362,7 +362,7 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example1.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B1"
++ Parser failure in state=steps at line 5: "Background: B1"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -387,6 +387,6 @@ Feature: Background
+ When I run "behave -f plain -T features/background_sad_example2.feature"
+ Then it should fail with:
+ """
+- Parser failure in state steps, at line 5: "Background: B2 (XFAIL)"
++ Parser failure in state=steps at line 5: "Background: B2 (XFAIL)"
+ REASON: Background should not be used here.
+ """
+diff --git a/features/parser.background.sad_cases.feature b/features/parser.background.sad_cases.feature
+index 37956ad..eb234ae 100644
+--- a/features/parser.background.sad_cases.feature
++++ b/features/parser.background.sad_cases.feature
+@@ -37,7 +37,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature":
+- Parser failure in state taggable_statement, at line 4: "Background: Oops..."
++ Parser failure in state=taggable_statement at line 4: "Background: Oops..."
+ REASON: Background does not support tags.
+ """
+
+@@ -57,7 +57,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ Then it should fail with
+ """
+ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature":
+- Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=steps at line 6: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -77,7 +77,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)"
++ Parser failure in state=taggable_statement at line 7: "Background: Oops, too late (after Scenario)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -100,7 +100,7 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=steps at line 10: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+
+@@ -124,6 +124,6 @@ Feature: Ensure that BAD/SAD Use cases of Background are detected
+ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature"
+ Then it should fail with
+ """
+- Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)"
++ Parser failure in state=taggable_statement at line 11: "Background: Oops, too late (after Scenario Outline)"
+ REASON: Background may not occur after Scenario/ScenarioOutline.
+ """
+diff --git a/features/parser.feature.sad_cases.feature b/features/parser.feature.sad_cases.feature
+index d89d9b7..0e12d9f 100644
+--- a/features/parser.feature.sad_cases.feature
++++ b/features/parser.feature.sad_cases.feature
+@@ -84,7 +84,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/only_text.feature":
+- Parser failure in state init, at line 1: "This File: Contains only text without keywords."
++ Parser failure in state=init at line 1: "This File: Contains only text without keywords."
+ REASON: No feature found.
+ """
+
+@@ -103,7 +103,7 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature":
+- Parser failure in state init, at line 1: "Scenario:"
++ Parser failure in state=init at line 1: "Scenario:"
+ REASON: Scenario may not occur before Feature.
+ """
+
+@@ -139,6 +139,6 @@ Feature: Parsing a Feature File without a Feature or with several Features
+ Then it should fail with:
+ """
+ Failed to parse "{__WORKDIR__}/features/two_features.feature":
+- Parser failure in state steps, at line 7: "Feature: F2"
++ Parser failure in state=steps at line 7: "Feature: F2"
+ REASON: Multiple features in one file are not supported.
+ """
+diff --git a/issue.features/issue0148.feature b/issue.features/issue0148.feature
+index 4387795..667d196 100644
+--- a/issue.features/issue0148.feature
++++ b/issue.features/issue0148.feature
+@@ -67,7 +67,7 @@ Feature: Issue #148: Substeps do not fail
+ And the command output should contain:
+ """
+ ParserError: Failed to parse <string>:
+- Parser failure in state steps, at line 2: "I do something stupid"
++ Parser failure in state=steps at line 2: "I do something stupid"
+ """
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
new file mode 100644
index 000000000..711233ff4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
@@ -0,0 +1,82 @@
+From 8e7d3ade79d4b55d0704ba25dc4d0082b64a1315 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Feb 2020 20:30:53 +0100
+Subject: [PATCH] Clarify select-by-name uses regex pattern (related to: issue
+ #810)
+
+Clarifiy select-by-name uses regex pattern:
+
+* Adapt command-line option --name help text
+* Update command-line args docs for behave
+---
+ CHANGES.rst | 4 ++++
+ behave/configuration.py | 10 +++++-----
+ docs/behave.rst | 12 ++++++------
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 5653492..d0bf6fd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -31,6 +31,10 @@ ENHANCEMENTS:
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
++CLARIFICATION:
++
++* issue #810: Clarify select-by-name using regex pattern (submitted by: xv-chris-w)
++
+ FIXED:
+
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+diff --git a/behave/configuration.py b/behave/configuration.py
+index bd8b039..65e2e3e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -164,11 +164,11 @@ options = [
+ override a configuration file setting.""")),
+
+ (("-n", "--name"),
+- dict(action="append",
+- help="""Only execute the feature elements which match part
+- of the given name. If this option is given more
+- than once, it will match against all the given
+- names.""")),
++ dict(action="append", metavar="NAME_PATTERN",
++ help="""Select feature elements (scenarios, ...) to run
++ which match part of the given name (regex pattern).
++ If this option is given more than once,
++ it will match against all the given names.""")),
+
+ (("--no-capture",),
+ dict(action="store_false", dest="stdout_capture",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index dfb390a..25ce523 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -95,9 +95,9 @@ You may see the same information presented below at any time using ``behave
+
+ .. option:: -n, --name
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. option:: --no-capture
+
+@@ -449,9 +449,9 @@ Configuration Parameters
+
+ .. describe:: name : sequence<text>
+
+- Only execute the feature elements which match part of the given name.
+- If this option is given more than once, it will match against all
+- the given names.
++ Select feature elements (scenarios, ...) to run which match part of
++ the given name (regex pattern). If this option is given more than
++ once, it will match against all the given names.
+
+ .. index::
+ single: configuration param; stdout_capture
diff --git a/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
new file mode 100644
index 000000000..b20903214
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
@@ -0,0 +1,34 @@
+From cb1b3d768367a129b944588b8ddf2971c3da26b9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:31:54 +0200
+Subject: [PATCH] FIX: Cross-reference problem (copy+paste) in Rule class
+ docstring.
+
+---
+ behave/model.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/behave/model.py b/behave/model.py
+index f46d2c2..cb69f9e 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -84,8 +84,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ .. attribute:: keyword
+
+- This is the keyword as seen in the *feature file*. In English this will
+- be "Feature" or "Rule".
++ This is the keyword as seen in the *feature file*.
++ In English this will be "Feature" or "Rule".
+
+ .. attribute:: name
+
+@@ -671,7 +671,7 @@ class Rule(ScenarioContainer):
+
+
+ .. versionadded:: 1.2.7
+- .. _`feature`: gherkin.html#rule
++ .. _`rule`: gherkin.html#rule
+ """
+ type = "rule"
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
new file mode 100644
index 000000000..1cd96abcc
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
@@ -0,0 +1,195 @@
+From f17fac59d4c0f162193baa4bedc3e8df8d11eb34 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:33:44 +0200
+Subject: [PATCH] DOCS: Update API description for "Runner Operation".
+
+* Provide description Gherkin grammar containments
+* Add description for Rule(s).
+---
+ docs/api.rst | 137 ++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 96 insertions(+), 41 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 5e4b7b4..39fa755 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -196,24 +196,34 @@ Environment File Functions
+ The environment.py module may define code to run before and after certain
+ events during your testing:
+
+-**before_step(context, step), after_step(context, step)**
+- These run before and after every step. The step passed in is an instance
+- of :class:`~behave.model.Step`.
++**before_all(context), after_all(context)**
++ These run before and after the whole shooting match.
++
++**before_feature(context, feature), after_feature(context, feature)**
++ These run before and after each feature is executed.
++ The feature object, that is passed in, is an instance of :class:`~behave.model.Feature`.
++
++**before_rule(context, rule), after_rule(context, rule)**
++ These run before and after each rule is execured.
++ The rule object, that is passed in, is an instance of :class:`~behave.model.Rule`.
+
+ **before_scenario(context, scenario), after_scenario(context, scenario)**
+- These run before and after each scenario is run. The scenario passed in is an
+- instance of :class:`~behave.model.Scenario`.
++ These run before and after each scenario is run.
++ The scenario object, that is passed in, is an instance of :class:`~behave.model.Scenario`.
+
+-**before_feature(context, feature), after_feature(context, feature)**
+- These run before and after each feature file is exercised. The feature
+- passed in is an instance of :class:`~behave.model.Feature`.
++**before_step(context, step), after_step(context, step)**
++ These run before and after every step.
++ The step object, that is passed in, is an instance of :class:`~behave.model.Step`.
+
+ **before_tag(context, tag), after_tag(context, tag)**
+ These run before and after a section tagged with the given name. They are
+ invoked for each tag encountered in the order they're found in the
+- feature file. See :ref:`controlling things with tags`. The tag passed in is
+- an instance of :class:`~behave.model.Tag` and because it's a subclass of
+- string you can do simple tests like:
++ feature file. See :ref:`controlling things with tags`.
++
++ Taggable statements are: Feature, Rule, Scenario, ScenarioOutline, Examples.
++
++ The tag, that is passed in, is an instance of :class:`~behave.model.Tag` and
++ because it's a subclass of string you can do simple tests like:
+
+ .. code-block:: python
+
+@@ -227,8 +237,6 @@ events during your testing:
+ else:
+ context.browser = webdriver.PlainVanilla()
+
+-**before_all(context), after_all(context)**
+- These run before and after the whole shooting match.
+
+
+ Some Useful Environment Ideas
+@@ -311,42 +319,87 @@ Use Fixtures
+ Runner Operation
+ ================
+
+-Given all the code that could be run by *behave*, this is the order in
+-which that code is invoked (if they exist.)
++The execution of code is based on the Gherkin description in `*.feature` files.
++The following section provides a short overview of the hierarchical containment
++that is possible in the Gherkin grammer:
+
+ .. parsed-literal::
+
+- before_all
+- for feature in all_features:
+- before_feature
+- for scenario in feature.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ # -- SIMPLIFIED GHERKIN GRAMMAR (for Gherkin v6):
++ # CARDINALITY DECORATOR: '*' means 0..N (many), '?' means 0..1 (optional)
++ # EXAMPLE: Feature
++ # A Feature can have many Tags (as TaggableStatement: zero or more tags before its keyword).
++ # A Feature can have an optional Background.
++ # A Feature can have many Scenario(s), meaning zero or more Scenarios.
++ # A Feature can have many ScenarioOutline(s).
++ # A Feature can have many Rule(s).
++ Feature(TaggableStatement):
++ Background?
++ Scenario*
++ ScenarioOutline*
++ Rule*
++
++ Background:
++ Step* # Background steps are injected into any Scenario of its scope.
++
++ Scenario(TaggableStatement):
++ Step*
+
+-If the feature contains scenario outlines then there is an additional loop
+-over all the scenarios in the outline making the running look like this:
++ ScenarioOutline(ScenarioTemplateWithPlaceholders):
++ Scenario* # Rendered Template by using ScenarioOutline.Examples.rows placeholder values.
++
++ Rule(TaggableStatement):
++ Background? # Behave-specific extension (after removal from final Gherkin v6).
++ Scenario*
++ ScenarioOutline*
++
++
++Given all the code that could be run by *behave*,
++this is the order in which that code is invoked (if they exist.)
+
+ .. parsed-literal::
+
+- before_all
++ # -- PSEUDO-CODE:
++ # HOOK: before_tag(), after_tag() is called for Feature, Rule, Scenario
++ ctx = createContext()
++ call-optional-hook before_all(ctx)
+ for feature in all_features:
+- before_feature
+- for outline in feature.scenarios:
+- for scenario in outline.scenarios:
+- before_scenario
+- for step in scenario.steps:
+- before_step
+- step.run()
+- after_step
+- after_scenario
+- after_feature
+- after_all
++ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_feature(ctx, feature)
++ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
++ execute_run_item(ctx, run_item)
++ call-optional-hook after_feature(ctx, feature)
++ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
++ call-optional-hook after_all(ctx)
++
++ function execute_run_item(run_item, ctx):
++ if run_item isa Rule:
++ # -- CASE: Rule
++ rule = run_item
++ for tag in rule.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_rule(ctx, rule)
++ for run_item in rule.run_items: # CAN BE: Scenario, ScenarioOutline
++ execute_run_item(run_item, ctx)
++ call-optional-hook after_rule(ctx, rule)
++ for tag in rule.tags: call-optional-hook after_tag(ctx, tag)
++ else if run_item isa ScenarioOutline:
++ # -- CASE: ScenarioOutline
++ # HINT: All Scenarios are already created from Example(s) rows.
++ scenario_outline = run_item
++ for scenario in scenario_outline.scenarios:
++ execute_run_item(scenario, ctx)
++ else if run_item isa Scenario:
++ # -- CASE: Scenario
++ # HINT: Background steps are injected before scenario steps.
++ scenario = run_item
++ for tag in scenario.tags: call-optional-hook before_tag(ctx, tag)
++ call-optional-hook before_scenario(ctx, scenario)
++ for step in scenario.steps:
++ call-optional-hook before_step(ctx, step)
++ step.run(ctx)
++ call-optional-hook after_step(ctx, step)
++ call-optional-hook after_scenario(ctx, scenario)
++ for tag in scenario.tags: call-optional-hook after_tag(ctx, tag)
+
+
+ Model Objects
+@@ -397,6 +450,8 @@ be:
+
+ .. autoclass:: behave.model.Feature
+
++.. autoclass:: behave.model.Rule
++
+ .. autoclass:: behave.model.Background
+
+ .. autoclass:: behave.model.Scenario
diff --git a/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
new file mode 100644
index 000000000..5fe3f774a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
@@ -0,0 +1,22 @@
+From 4b27f39342afa14f1554458af808958350b8aeda Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 13 Apr 2020 10:38:17 +0200
+Subject: [PATCH] FIX DOCS: Runner operations typo.
+
+---
+ docs/api.rst | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 39fa755..4763ad6 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -367,7 +367,7 @@ this is the order in which that code is invoked (if they exist.)
+ for tag in feature.tags: call-optional-hook before_tag(ctx, tag)
+ call-optional-hook before_feature(ctx, feature)
+ for run_item in feature.run_items: # CAN BE: Rule, Scenario, ScenarioOutline
+- execute_run_item(ctx, run_item)
++ execute_run_item(run_item, ctx)
+ call-optional-hook after_feature(ctx, feature)
+ for tag in feature.tags: call-optional-hook after_tag(ctx, tag)
+ call-optional-hook after_all(ctx)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
new file mode 100644
index 000000000..928fb4511
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
@@ -0,0 +1,295 @@
+From 3c38942c26f91cb5c3047b8c91ba2e8e5a1511a0 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 23 Sep 2020 23:22:45 +0200
+Subject: [PATCH] issue #740: Enhancement: Context.add_cleanup() with
+ layer-name
+
+Context.add_cleanup() can choose which cleanup layer to use (outer layer).
+This provides the possibility that a cleanup-funcion, registered with
+Context.add_cleanup(cleanup_func, ...) to be called upon leaving
+the outer context stack frames.
+
+Submitted by: nizwiz
+Requested by: dcvmoole
+---
+ CHANGES.rst | 7 +++
+ behave/model.py | 4 +-
+ behave/runner.py | 45 ++++++++++++---
+ tests/unit/test_context_cleanups.py | 86 ++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 12 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d0bf6fd..d758364 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,7 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+@@ -65,6 +66,12 @@ DOCUMENTATION:
+ * pull #684: Fix typo in "install.rst" (provided by: mstred)
+ * pull #628: Changed pythonhosted.org links to readthedocs.io (provided by: chrisbrake)
+
++BREAKING CHANGES (naming):
++
++* behave.runner.Context._push(layer=None): Was Context._push(layer_name=None)
++* behave.runner.scoped_context_layer(context, layer=None):
++ Was scoped_context_layer(context.layer_name=None)
++
+
+ .. _`cucumber-tag-expressions`: https://pypi.org/project/cucumber-tag-expressions/
+
+diff --git a/behave/model.py b/behave/model.py
+index cb69f9e..f1ec725 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_before_entity = "before_{0}".format(entity_name)
+ hook_after_entity = "after_{0}".format(entity_name)
+
+- runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
++ runner.context._push(layer=entity_name) # pylint: disable=protected-access
+ runner.context.tags = set(self.tags)
+ self._setup_context_for_run(runner.context)
+
+@@ -1136,7 +1136,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+ dry_run_scenario = run_scenario and runner.config.dry_run
+ self.was_dry_run = dry_run_scenario
+
+- runner.context._push(layer_name="scenario") # pylint: disable=protected-access
++ runner.context._push(layer="scenario") # pylint: disable=protected-access
+ runner.context.scenario = self
+ runner.context.tags = set(self.effective_tags)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index 6b20937..d01bff0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -145,7 +145,7 @@ class Context(object):
+ tries to overwrite a user-set variable.
+
+ You may use the "in" operator to test whether a certain value has been set
+- on the context, for example:
++ on the context, for example::
+
+ "feature" in context
+
+@@ -158,6 +158,7 @@ class Context(object):
+ .. _`configuration file section names`: behave.html#configuration-files
+ """
+ # pylint: disable=too-many-instance-attributes
++ LAYER_NAMES = ["testrun", "feature", "rule", "scenario"]
+ FAIL_ON_CLEANUP_ERRORS = True
+
+ def __init__(self, runner):
+@@ -245,16 +246,15 @@ class Context(object):
+ del cleanup_errors # -- ENSURE: Release other exception frames.
+ six.reraise(*first_cleanup_erro_info)
+
+-
+- def _push(self, layer_name=None):
++ def _push(self, layer=None):
+ """Push a new layer on the context stack.
+- HINT: Use layer_name values: "scenario", "feature", "testrun".
++ HINT: Use layer values: "testrun", "feature", "rule, "scenario".
+
+- :param layer_name: Layer name to use (or None).
++ :param layer: Layer name to use (or None).
+ """
+ initial_data = {"@cleanups": []}
+- if layer_name:
+- initial_data["@layer"] = layer_name
++ if layer:
++ initial_data["@layer"] = layer
+ self._stack.insert(0, initial_data)
+
+ def _pop(self):
+@@ -426,6 +426,20 @@ class Context(object):
+ self.text = original_text
+ return True
+
++ def _select_stack_frame_by_layer(self, layer):
++ """Select context stack frame by layer name.
++
++ :param layer: Layer name (as string).
++ :return: Selected frame object (if any)
++ :raises: LookupError, if layer was not found.
++ """
++ for frame in self._stack:
++ frame_layer = frame.get("@layer", None)
++ if layer == frame_layer:
++ return frame
++ # -- OOPS, NOT FOUND:
++ raise LookupError("Context.stack: layer=%s not found" % layer)
++
+ def add_cleanup(self, cleanup_func, *args, **kwargs):
+ """Adds a cleanup function that is called when :meth:`Context._pop()`
+ is called. This is intended for user-cleanups.
+@@ -433,10 +447,21 @@ class Context(object):
+ :param cleanup_func: Callable function
+ :param args: Args for cleanup_func() call (optional).
+ :param kwargs: Kwargs for cleanup_func() call (optional).
++
++ .. note:: RESERVED :obj:`layer` : optional-string
++
++ The keyword argument ``layer="LAYER_NAME"`` can to be used to
++ assign the :obj:`cleanup_func` to specific a layer on the context stack
++ (instead of the current layer).
++
++ Known layer names are: "testrun", "feature", "rule", "scenario"
++
++ .. seealso:: :attr:`.Context.LAYER_NAMES`
+ """
+ # MAYBE:
+ assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)"
+ assert self._stack
++ layer = kwargs.pop("layer", None)
+ if args or kwargs:
+ def internal_cleanup_func():
+ cleanup_func(*args, **kwargs)
+@@ -444,6 +469,8 @@ class Context(object):
+ internal_cleanup_func = cleanup_func
+
+ current_frame = self._stack[0]
++ if layer:
++ current_frame = self._select_stack_frame_by_layer(layer)
+ if cleanup_func not in current_frame["@cleanups"]:
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+@@ -477,7 +504,7 @@ def use_context_with_mode(context, mode):
+
+
+ @contextlib.contextmanager
+-def scoped_context_layer(context, layer_name=None):
++def scoped_context_layer(context, layer=None):
+ """Provides context manager for context layer (push/do-something/pop cycle).
+
+ .. code-block::
+@@ -487,7 +514,7 @@ def scoped_context_layer(context, layer_name=None):
+ """
+ # pylint: disable=protected-access
+ try:
+- context._push(layer_name)
++ context._push(layer)
+ yield context
+ finally:
+ context._pop()
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index bf0ab50..c32c572 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -23,6 +23,9 @@ import pytest
+ def cleanup_func():
+ pass
+
++def cleanup_func_with_args(*args, **kwargs):
++ pass
++
+ class CleanupFunction(object):
+ def __init__(self, name="CLEANUP-FUNC", listener=None):
+ self.name = name
+@@ -42,7 +45,6 @@ class CallListener(object):
+ self.collected.append(message)
+
+
+-
+ # ------------------------------------------------------------------------------
+ # TESTS:
+ # ------------------------------------------------------------------------------
+@@ -145,6 +147,24 @@ class TestContextCleanup(object):
+ my_cleanup_B2M.assert_called_once()
+ my_cleanup_B3M.assert_called_once()
+
++ def test_add_cleanup_with_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3)
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_args_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context):
++ context.add_cleanup(my_cleanup, 1, 2, 3, name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3, name="alice")
++
+ def test_add_cleanup__rejects_noncallable_cleanup_func(self):
+ class NonCallable(object): pass
+ non_callable = NonCallable()
+@@ -218,3 +238,67 @@ class TestContextCleanup(object):
+ assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1]
+ assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1]
+
++
++class TestContextCleanupWithLayer(object):
++ """Tests :meth:`behave.runner.Context.add_cleanup()`
++ with layer parameter.
++
++ :meth:`cleanup_func()` is called when Context layer is removed/popped.
++ """
++
++ def test_add_cleanup_with_known_layer(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_layer_and_args(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, 1, 2, 3, layer="scenario")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(1, 2, 3)
++
++ def test_add_cleanup_with_known_layer_and_kwargs(self):
++ my_cleanup = Mock(spec=cleanup_func_with_args)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="scenario", name="alice")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once_with(name="alice")
++
++ def test_add_cleanup_with_known_deeper_layer2(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ # CALLS-HERE: context._pop()
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_known_deeper_layer3(self):
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context, layer="testrun"):
++ with scoped_context_layer(context, layer="feature"):
++ with scoped_context_layer(context, layer="scenario"):
++ context.add_cleanup(my_cleanup, layer="feature")
++ my_cleanup.assert_not_called()
++ my_cleanup.assert_called_once() # LEFT: layer="feature"
++ my_cleanup.assert_called_once()
++
++ def test_add_cleanup_with_unknown_layer_raises_lookup_error(self):
++ """Cleanup function is not registered"""
++ my_cleanup = Mock(spec=cleanup_func)
++ context = Context(runner=Mock())
++ with scoped_context_layer(context): # CALLS-HERE: context._push()
++ with pytest.raises(LookupError) as error:
++ context.add_cleanup(my_cleanup, layer="other")
++ my_cleanup.assert_not_called()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
new file mode 100644
index 000000000..d2b36d5f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
@@ -0,0 +1,37 @@
+From fa3987c3504d4857b3b0cb3e0e1767a69bdadd04 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 20 Oct 2020 22:40:46 +0200
+Subject: [PATCH] UPDATE: parse >= 1.18.0 (parse versions: 1.16.0 .. 1.17.x has
+ a problem; parse issue #119, #121)
+
+---
+ py.requirements/basic.txt | 2 +-
+ setup.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index ad5b9a6..f976748 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -9,7 +9,7 @@
+ # ============================================================================
+
+ cucumber-tag-expressions >= 1.1.2
+-parse >= 1.8.2
++parse >= 1.18.0
+ parse_type >= 0.4.2
+ six >= 1.12.0
+
+diff --git a/setup.py b/setup.py
+index 23f6654..fd89bda 100644
+--- a/setup.py
++++ b/setup.py
+@@ -77,7 +77,7 @@ setup(
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+ "cucumber-tag-expressions >= 1.1.2",
+- "parse >= 1.9.1",
++ "parse >= 1.18.0",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
new file mode 100644
index 000000000..1a3d2ebba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
@@ -0,0 +1,34 @@
+From a3643426f04c590cdcd48be61237ee2ff723f551 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 2 Nov 2020 17:50:10 +0100
+Subject: [PATCH] RELATED TO: Duplicated steps/AmbiguousStepErrors
+
+* Remove @xfail from third scenario (it worked already for some time).
+* Added more detailled description to third scenario.
+---
+ features/step.duplicated_step.feature | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
+index 396cca2..f204307 100644
+--- a/features/step.duplicated_step.feature
++++ b/features/step.duplicated_step.feature
+@@ -76,8 +76,17 @@ Feature: Duplicated Step Definitions
+ # File "features/steps/bob2_steps.py", line 3, in <module>
+ # """
+
+- @xfail
++
+ Scenario: Duplicated Same Step Definition via import from another File
++
++ VERIFY THAT: Duplicated step-detection works.
++ Duplicated step-registration occured through a twice imported step-module:
++ First registration may occurs by step-loading the step-module,
++ second registration due to import of first step-module.
++
++ The step registry detects that the same step-function should be registered
++ another time and ignores it (step is already registered).
++
+ Given a new working directory
+ And a file named "features/steps/charly1_steps.py" with:
+ """
diff --git a/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
new file mode 100644
index 000000000..ff24a58f4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
@@ -0,0 +1,21 @@
+From 255ed96600a80fc4be0fda93b3db790112b08cea Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 08:43:32 +0000
+Subject: [PATCH] Add renovate.json
+
+---
+ renovate.json | 5 +++++
+ 1 file changed, 5 insertions(+)
+ create mode 100644 renovate.json
+
+diff --git a/renovate.json b/renovate.json
+new file mode 100644
+index 0000000..f45d8f1
+--- /dev/null
++++ b/renovate.json
+@@ -0,0 +1,5 @@
++{
++ "extends": [
++ "config:base"
++ ]
++}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
new file mode 100644
index 000000000..b49257583
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
@@ -0,0 +1,175 @@
+From 65b9ec6a6a36df4353e36acd512ffbb3d2b39679 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:28:59 +0100
+Subject: [PATCH] PRPEPARE-RENOVATE: With adaptions
+
+* Provide locations/patterns for pip requirements file(s)
+* Move "renovate.json" to ".github/"
+---
+ .github/renovate.json | 13 ++++++++
+ MANIFEST.in | 4 +--
+ issue.features/README.rst | 32 +++++++++++++++++++
+ issue.features/README.txt | 17 ----------
+ .../{requirements.txt => py.requirements.txt} | 7 ++--
+ py.requirements/{README.txt => README.rst} | 0
+ py.requirements/testing.txt | 4 ++-
+ renovate.json | 5 ---
+ 8 files changed, 53 insertions(+), 29 deletions(-)
+ create mode 100644 .github/renovate.json
+ create mode 100644 issue.features/README.rst
+ delete mode 100644 issue.features/README.txt
+ rename issue.features/{requirements.txt => py.requirements.txt} (82%)
+ rename py.requirements/{README.txt => README.rst} (100%)
+ delete mode 100644 renovate.json
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+new file mode 100644
+index 0000000..3399a00
+--- /dev/null
++++ b/.github/renovate.json
+@@ -0,0 +1,13 @@
++{
++ "extends": [
++ "config:base"
++ ],
++ "pip_requirements": {
++ "fileMatch": [
++ "py.requirements/all.txt",
++ "py.requirements/*.txt",
++ "tasks/py.requirements.txt",
++ "issue.features/py.requirements.txt"
++ ]
++}
++}
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 60c2601..84d20f4 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -28,8 +28,8 @@ recursive-include tasks *.py *.zip *.txt *.rst
+ recursive-include tools *.feature *.py *.yml *.sh
+ recursive-include features *.feature *.py *.txt
+ recursive-include issue.features *.feature *.py *.txt
+-recursive-include more.features *.feature *.py *.txt
+-recursive-include py.requirements *.txt
++recursive-include more.features *.feature *.py *.txt *.rst
++recursive-include py.requirements *.txt *.rst
+
+ prune .tox
+ prune .venv*
+diff --git a/issue.features/README.rst b/issue.features/README.rst
+new file mode 100644
+index 0000000..1d1d980
+--- /dev/null
++++ b/issue.features/README.rst
+@@ -0,0 +1,32 @@
++issue.features:
++===============================================================================
++
++:Requires: Python >= 2.7 or Python >= 3.5
++
++This directory contains behave self-tests to ensure that behave related
++issues are fixed.
++
++PROCEDURE:
++
++ * ONCE: Install python requirements ("py.requirements.txt")
++ * Run the tests with behave
++
++.. code-block:: shell
++
++ # -- FOR:
++ # pip: For python2.7 or python3 (depends on platform and/or user))
++ # pip3: For python3
++ pip install -U -r issue.features/testing.txt
++ pip3 install -U -r issue.features/testing.txt
++
++
++ALTERNATIVE:
++
++.. code-block:: shell
++
++ pip install -U -r py.requirements/testing.txt
++ pip3 install -U -r py.requirements/testing.txt
++
++.. code-block:: shell
++
++ bin/behave -f progress issue.features/
+diff --git a/issue.features/README.txt b/issue.features/README.txt
+deleted file mode 100644
+index af499f3..0000000
+--- a/issue.features/README.txt
++++ /dev/null
+@@ -1,17 +0,0 @@
+-issue.features:
+-===============================================================================
+-
+-:Status: PREPARED (fixes are being applied).
+-:Requires: Python >= 2.6 (due to step implementations)
+-
+-This directory contains behave self-tests to ensure that behave related
+-issues are fixed.
+-
+-PROCEDURE:
+-
+- * ONCE: Install python requirements ("requirements.txt")
+- * Run the tests with behave
+-
+-::
+-
+- bin/behave -f progress issue.features/
+diff --git a/issue.features/requirements.txt b/issue.features/py.requirements.txt
+similarity index 82%
+rename from issue.features/requirements.txt
+rename to issue.features/py.requirements.txt
+index d0c4bab..f0def9d 100644
+--- a/issue.features/requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -1,12 +1,11 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/
+ # ============================================================================
+-# REQUIRES: Python >= 2.5
+-# REQUIRES: Python >= 3.2
++# REQUIRES: Python >= 2.7
++# REQUIRES: Python >= 3.5
+ # DESCRIPTION:
+ # pip install -r <THIS_FILE>
+ #
+ # ============================================================================
+
+-PyHamcrest >= 1.6
+-
++PyHamcrest >= 2.0.2
+diff --git a/py.requirements/README.txt b/py.requirements/README.rst
+similarity index 100%
+rename from py.requirements/README.txt
+rename to py.requirements/README.rst
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 85b0908..5ccdda8 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,10 +8,12 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest >= 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+ # HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++-r ../issue.features/py.requirements.txt
+diff --git a/renovate.json b/renovate.json
+deleted file mode 100644
+index f45d8f1..0000000
+--- a/renovate.json
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{
+- "extends": [
+- "config:base"
+- ]
+-}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
new file mode 100644
index 000000000..7882ebd21
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
@@ -0,0 +1,36 @@
+From 4424234ade658ab2d943b77c48dfd9a6e63b2c05 Mon Sep 17 00:00:00 2001
+From: Renovate Bot <bot@renovateapp.com>
+Date: Wed, 4 Nov 2020 09:32:34 +0000
+Subject: [PATCH] Pin dependencies
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ tasks/py.requirements.txt | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index f0def9d..38f452d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest >= 2.0.2
++PyHamcrest==2.0.2
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 810f834..9c82d11 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -8,9 +8,9 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-invoke >= 1.2.0
++invoke==1.4.1
+ pycmd
+-six >= 1.12.0
++six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
diff --git a/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
new file mode 100644
index 000000000..b821f923e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
@@ -0,0 +1,31 @@
+From ba70dd5d6c97b2732600adcb4764206b936a0a36 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:02 +0100
+Subject: [PATCH] renovate: Extend pip requirements file list.
+
+---
+ .github/renovate.json | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/.github/renovate.json b/.github/renovate.json
+index 3399a00..9b72f76 100644
+--- a/.github/renovate.json
++++ b/.github/renovate.json
+@@ -4,10 +4,15 @@
+ ],
+ "pip_requirements": {
+ "fileMatch": [
+- "py.requirements/all.txt",
+ "py.requirements/*.txt",
++ "py.requirements/all.txt",
++ "py.requirements/basic.txt",
++ "py.requirements/develop.txt",
++ "py.requirements/testing.txt",
++ "py.requirements/ci.tox.txt",
++ "py.requirements/ci.travis.txt",
+ "tasks/py.requirements.txt",
+ "issue.features/py.requirements.txt"
+ ]
+-}
++ }
+ }
diff --git a/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
new file mode 100644
index 000000000..67d1c4b53
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
@@ -0,0 +1,92 @@
+From f9a1c7478df6b8986267b68cb09ba62b635b57e6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 10:54:48 +0100
+Subject: [PATCH] PIN REQUIREMENTS: Extend to all places. PINNED: invoke, six,
+ PyHamcrest
+
+---
+ issue.features/py.requirements.txt | 2 +-
+ py.requirements/basic.txt | 2 +-
+ py.requirements/ci.tox.txt | 2 +-
+ py.requirements/ci.travis.txt | 2 +-
+ py.requirements/develop.txt | 4 +---
+ py.requirements/testing.txt | 2 +-
+ 6 files changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 38f452d..6e3cf83 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,4 @@
+ #
+ # ============================================================================
+
+-PyHamcrest==2.0.2
++PyHamcrest == 2.0.2
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index f976748..6c644e0 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.2
+ parse >= 1.18.0
+ parse_type >= 0.4.2
+-six >= 1.12.0
++six == 1.15.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 4001bc2..20ae791 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -6,7 +6,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index de4120a..c69445c 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -5,7 +5,7 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 1.9
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index a92ee5f..e7dc418 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -3,9 +3,7 @@
+ # ============================================================================
+
+ # -- BUILD-TOOL:
+-# PREPARE USAGE: invoke
+-# ALREADY: six >= 1.11.0
+-invoke >= 1.2.0
++invoke == 1.4.1
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5ccdda8..d3bca18 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,7 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+ pytest-html >= 1.19.0
+ mock >= 2.0
+-PyHamcrest >= 2.0.2
++PyHamcrest == 2.0.2
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
new file mode 100644
index 000000000..1ced592e5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
@@ -0,0 +1,8116 @@
+From a84f76c5fe60f3f3780700ba69e6eeb5dda73521 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 4 Nov 2020 21:18:12 +0100
+Subject: [PATCH] tasks: Add invoke_cleanup (replaces: _tasklet_cleanup),
+ remove _vendor/ directory.
+
+---
+ py.requirements/docs.txt | 3 +-
+ tasks/__init__.py | 10 +-
+ tasks/_setup.py | 10 +-
+ tasks/_tasklet_cleanup.py | 295 -------
+ tasks/_vendor/README.rst | 35 -
+ tasks/_vendor/invoke.zip | Bin 172281 -> 0 bytes
+ tasks/_vendor/path.py | 1725 -------------------------------------
+ tasks/_vendor/pathlib.py | 1280 ---------------------------
+ tasks/_vendor/six.py | 868 -------------------
+ tasks/docs.py | 33 +-
+ tasks/invoke_cleanup.py | 447 ++++++++++
+ tasks/py.requirements.txt | 2 +-
+ tasks/release.py | 2 +-
+ tasks/test.py | 3 +-
+ 14 files changed, 497 insertions(+), 4216 deletions(-)
+ delete mode 100644 tasks/_tasklet_cleanup.py
+ delete mode 100644 tasks/_vendor/README.rst
+ delete mode 100644 tasks/_vendor/invoke.zip
+ delete mode 100644 tasks/_vendor/path.py
+ delete mode 100644 tasks/_vendor/pathlib.py
+ delete mode 100644 tasks/_vendor/six.py
+ create mode 100644 tasks/invoke_cleanup.py
+
+diff --git a/py.requirements/docs.txt b/py.requirements/docs.txt
+index 6839ba9..1384e00 100644
+--- a/py.requirements/docs.txt
++++ b/py.requirements/docs.txt
+@@ -3,7 +3,8 @@
+ # ============================================================================
+ # REQUIRES: pip >= 8.0
+
+-Sphinx >= 1.6
++sphinx >= 1.6
++sphinx-autobuild
+ sphinx_bootstrap_theme >= 0.6.0
+
+ # -- SUPPORT: sphinx-doc translations (prepared)
+diff --git a/tasks/__init__.py b/tasks/__init__.py
+index a572465..9ae899b 100644
+--- a/tasks/__init__.py
++++ b/tasks/__init__.py
+@@ -20,7 +20,7 @@ from __future__ import absolute_import
+ from . import _setup # pylint: disable=wrong-import-order
+ import os.path
+ import sys
+-INVOKE_MINVERSION = "1.2.0"
++INVOKE_MINVERSION = "1.4.0"
+ _setup.setup_path()
+ _setup.require_invoke_minversion(INVOKE_MINVERSION)
+
+@@ -35,7 +35,8 @@ import sys
+ from invoke import Collection
+
+ # -- TASK-LIBRARY:
+-from . import _tasklet_cleanup as cleanup
++# PREPARED: import invoke_cleanup as cleanup
++from . import invoke_cleanup as cleanup
+ from . import docs
+ from . import test
+ from . import release
+@@ -52,19 +53,18 @@ from . import develop
+ # TASK CONFIGURATION:
+ # -----------------------------------------------------------------------------
+ namespace = Collection()
+-# DISABLED: namespace.add_task(clean.clean)
+-# DISABLED: namespace.add_task(clean.clean_all)
+ namespace.add_collection(Collection.from_module(cleanup), name="cleanup")
+ namespace.add_collection(Collection.from_module(docs))
+ namespace.add_collection(Collection.from_module(test))
+ namespace.add_collection(Collection.from_module(release))
+ namespace.add_collection(Collection.from_module(develop))
+-cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
++# -- ENSURE: python cleanup is used for this project.
+ cleanup.cleanup_tasks.add_task(cleanup.clean_python)
+
+ # -- INJECT: clean configuration into this namespace
+ namespace.configure(cleanup.namespace.configuration())
++namespace.configure(test.namespace.configuration())
+ if sys.platform.startswith("win"):
+ # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
+ from ._compat_shutil import which
+diff --git a/tasks/_setup.py b/tasks/_setup.py
+index eda5ca9..e69ec82 100644
+--- a/tasks/_setup.py
++++ b/tasks/_setup.py
+@@ -14,7 +14,7 @@ import sys
+ HERE = os.path.dirname(__file__)
+ TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
+ INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
+-INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name
++INVOKE_BUNDLE_VERSION = "1.4.0"
+
+ DEBUG_SYSPATH = False
+
+@@ -25,6 +25,7 @@ DEBUG_SYSPATH = False
+ class VersionRequirementError(SystemExit):
+ pass
+
++
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -32,7 +33,7 @@ def setup_path(invoke_minversion=None):
+ """Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
+ # print("INVOKE.tasks: setup_path")
+ if not os.path.isdir(TASKS_VENDOR_DIR):
+- print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
++ # SILENT: print("SKIP: TASKS_VENDOR_DIR=%s is missing" % os.path.relpath(TASKS_VENDOR_DIR))
+ return
+ elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
+ # -- SETUP ALREADY DONE:
+@@ -86,6 +87,7 @@ def require_invoke_minversion(min_version, verbose=False):
+ os.environ["INVOKE_VERSION"] = invoke_version
+ print("USING: invoke.version=%s" % invoke_version)
+
++
+ def need_vendor_bundles(invoke_minversion=None):
+ invoke_minversion = invoke_minversion or "0.0.0"
+ need_vendor_answers = []
+@@ -102,6 +104,7 @@ def need_vendor_bundles(invoke_minversion=None):
+ # return need_bundle1 or need_bundle2
+ return any(need_vendor_answers)
+
++
+ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ # -- REQUIRE: invoke
+ try:
+@@ -116,6 +119,7 @@ def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
+ need_bundle = True
+ return need_bundle
+
++
+ # -----------------------------------------------------------------------------
+ # UTILITY FUNCTIONS:
+ # -----------------------------------------------------------------------------
+@@ -125,11 +129,13 @@ def setup_path_for_bundle(bundle_path, pos=0):
+ return True
+ return False
+
++
+ def syspath_insert(pos, path):
+ if path in sys.path:
+ sys.path.remove(path)
+ sys.path.insert(pos, path)
+
++
+ def syspath_append(path):
+ if path in sys.path:
+ sys.path.remove(path)
+diff --git a/tasks/_tasklet_cleanup.py b/tasks/_tasklet_cleanup.py
+deleted file mode 100644
+index 2999bc6..0000000
+--- a/tasks/_tasklet_cleanup.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-# -*- coding: UTF-8 -*-
+-"""
+-Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
+-Simplifies writing common, composable and extendable cleanup tasks.
+-
+-PYTHON PACKAGE REQUIREMENTS:
+-* path.py >= 8.2.1 (as path-object abstraction)
+-* pathlib (for ant-like wildcard patterns; since: python > 3.5)
+-* pycmd (required-by: clean_python())
+-
+-clean task: Add Additional Directories and Files to be removed
+--------------------------------------------------------------------------------
+-
+-Create an invoke configuration file (YAML of JSON) with the additional
+-configuration data:
+-
+-.. code-block:: yaml
+-
+- # -- FILE: invoke.yaml
+- # USE: clean.directories, clean.files to override current configuration.
+- clean:
+- extra_directories:
+- - **/tmp/
+- extra_files:
+- - **/*.log
+- - **/*.bak
+-
+-
+-Registration of Cleanup Tasks
+-------------------------------
+-
+-Other task modules often have an own cleanup task to recover the clean state.
+-The :meth:`clean` task, that is provided here, supports the registration
+-of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed,
+-all registered cleanup tasks will be executed.
+-
+-EXAMPLE::
+-
+- # -- FILE: tasks/docs.py
+- from __future__ import absolute_import
+- from invoke import task, Collection
+- from tasklet_cleanup import cleanup_tasks, cleanup_dirs
+-
+- @task
+- def clean(ctx, dry_run=False):
+- "Cleanup generated documentation artifacts."
+- cleanup_dirs(["build/docs"])
+-
+- namespace = Collection(clean)
+- ...
+-
+- # -- REGISTER CLEANUP TASK:
+- cleanup_tasks.add_task(clean, "clean_docs")
+- cleanup_tasks.configure(namespace.configuration())
+-"""
+-
+-from __future__ import absolute_import, print_function
+-import os.path
+-import sys
+-import pathlib
+-from invoke import task, Collection
+-from invoke.executor import Executor
+-from invoke.exceptions import Exit, Failure, UnexpectedExit
+-from path import Path
+-
+-
+-# -----------------------------------------------------------------------------
+-# CLEANUP UTILITIES:
+-# -----------------------------------------------------------------------------
+-def cleanup_accept_old_config(ctx):
+- ctx.cleanup.directories.extend(ctx.clean.directories or [])
+- ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or [])
+- ctx.cleanup.files.extend(ctx.clean.files or [])
+- ctx.cleanup.extra_files.extend(ctx.clean.extra_files or [])
+-
+- ctx.cleanup_all.directories.extend(ctx.clean_all.directories or [])
+- ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or [])
+- ctx.cleanup_all.files.extend(ctx.clean_all.files or [])
+- ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or [])
+-
+-
+-def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False):
+- """Execute several cleanup tasks as part of the cleanup.
+-
+- REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks.
+-
+- :param ctx: Context object for the tasks.
+- :param cleanup_tasks: Collection of cleanup tasks (as Collection).
+- :param dry_run: Indicates dry-run mode (bool)
+- """
+- # pylint: disable=redefined-outer-name
+- executor = Executor(cleanup_tasks, ctx.config)
+- failure_count = 0
+- for cleanup_task in cleanup_tasks.tasks:
+- try:
+- print("CLEANUP TASK: %s" % cleanup_task)
+- executor.execute((cleanup_task, dict(dry_run=dry_run)))
+- except (Exit, Failure, UnexpectedExit) as e:
+- print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
+- failure_count += 1
+-
+- if failure_count:
+- print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
+-
+-
+-def cleanup_dirs(patterns, dry_run=False, workdir="."):
+- """Remove directories (and their contents) recursively.
+- Skips removal if directories does not exist.
+-
+- :param patterns: Directory name patterns, like "**/tmp*" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- warn2_counter = 0
+- for dir_pattern in patterns:
+- for directory in path_glob(dir_pattern, current_dir):
+- directory2 = directory.abspath()
+- if sys.executable.startswith(directory2):
+- # pylint: disable=line-too-long
+- print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
+- continue
+- elif directory2.startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- if warn2_counter <= 4:
+- print("SKIP-SUICIDE: '%s'" % directory)
+- warn2_counter += 1
+- continue
+-
+- if not directory.isdir():
+- print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
+- continue
+-
+- if dry_run:
+- print("RMTREE: %s (dry-run)" % directory)
+- else:
+- print("RMTREE: %s" % directory)
+- directory.rmtree_p()
+-
+-
+-def cleanup_files(patterns, dry_run=False, workdir="."):
+- """Remove files or files selected by file patterns.
+- Skips removal if file does not exist.
+-
+- :param patterns: File patterns, like "**/*.pyc" (as list).
+- :param dry_run: Dry-run mode indicator (as bool).
+- :param workdir: Current work directory (default=".")
+- """
+- current_dir = Path(workdir)
+- python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
+- error_message = None
+- error_count = 0
+- for file_pattern in patterns:
+- for file_ in path_glob(file_pattern, current_dir):
+- if file_.abspath().startswith(python_basedir):
+- # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT:
+- continue
+- if not file_.isfile():
+- print("REMOVE: %s (SKIPPED: Not a file)" % file_)
+- continue
+-
+- if dry_run:
+- print("REMOVE: %s (dry-run)" % file_)
+- else:
+- print("REMOVE: %s" % file_)
+- try:
+- file_.remove_p()
+- except os.error as e:
+- message = "%s: %s" % (e.__class__.__name__, e)
+- print(message + " basedir: "+ python_basedir)
+- error_count += 1
+- if not error_message:
+- error_message = message
+- if False and error_message:
+- class CleanupError(RuntimeError):
+- pass
+- raise CleanupError(error_message)
+-
+-
+-def path_glob(pattern, current_dir=None):
+- """Use pathlib for ant-like patterns, like: "**/*.py"
+-
+- :param pattern: File/directory pattern to use (as string).
+- :param current_dir: Current working directory (as Path, pathlib.Path, str)
+- :return Resolved Path (as path.Path).
+- """
+- if not current_dir:
+- current_dir = pathlib.Path.cwd()
+- elif not isinstance(current_dir, pathlib.Path):
+- # -- CASE: string, path.Path (string-like)
+- current_dir = pathlib.Path(str(current_dir))
+-
+- for p in current_dir.glob(pattern):
+- yield Path(str(p))
+-
+-
+-# -----------------------------------------------------------------------------
+-# GENERIC CLEANUP TASKS:
+-# -----------------------------------------------------------------------------
+-@task
+-def clean(ctx, dry_run=False):
+- """Cleanup temporary dirs/files to regain a clean state."""
+- cleanup_accept_old_config(ctx)
+- directories = ctx.cleanup.directories or []
+- directories.extend(ctx.cleanup.extra_directories or [])
+- files = ctx.cleanup.files or []
+- files.extend(ctx.cleanup.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run)
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+-
+-
+-@task(name="all", aliases=("distclean",))
+-def clean_all(ctx, dry_run=False):
+- """Clean up everything, even the precious stuff.
+- NOTE: clean task is executed first.
+- """
+- cleanup_accept_old_config(ctx)
+- directories = ctx.config.cleanup_all.directories or []
+- directories.extend(ctx.config.cleanup_all.extra_directories or [])
+- files = ctx.config.cleanup_all.files or []
+- files.extend(ctx.config.cleanup_all.extra_files or [])
+-
+- # -- PERFORM CLEANUP:
+- # HINT: Remove now directories, files first before cleanup-tasks.
+- cleanup_dirs(directories, dry_run=dry_run)
+- cleanup_files(files, dry_run=dry_run)
+- execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run)
+- clean(ctx, dry_run=dry_run)
+-
+-
+-@task(name="python")
+-def clean_python(ctx, dry_run=False):
+- """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
+- # MAYBE NOT: "**/__pycache__"
+- cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
+- dry_run=dry_run)
+- if not dry_run:
+- ctx.run("py.cleanup")
+- cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run)
+-
+-
+-# -----------------------------------------------------------------------------
+-# TASK CONFIGURATION:
+-# -----------------------------------------------------------------------------
+-CLEANUP_EMPTY_CONFIG = {
+- "directories": [],
+- "files": [],
+- "extra_directories": [],
+- "extra_files": [],
+-}
+-def make_cleanup_config(**kwargs):
+- config_data = CLEANUP_EMPTY_CONFIG.copy()
+- config_data.update(kwargs)
+- return config_data
+-
+-
+-namespace = Collection(clean_all, clean_python)
+-namespace.add_task(clean, default=True)
+-namespace.configure({
+- "cleanup": make_cleanup_config(
+- files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"]
+- ),
+- "cleanup_all": make_cleanup_config(
+- directories=[".venv*", ".tox", "downloads", "tmp"]
+- ),
+- # -- BACKWARD-COMPATIBLE: OLD-STYLE
+- "clean": CLEANUP_EMPTY_CONFIG.copy(),
+- "clean_all": CLEANUP_EMPTY_CONFIG.copy(),
+-})
+-
+-
+-# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
+-# NOTE: Can be used by other tasklets to register cleanup tasks.
+-cleanup_tasks = Collection("cleanup_tasks")
+-cleanup_all_tasks = Collection("cleanup_all_tasks")
+-
+-# -- EXTEND NORMAL CLEANUP-TASKS:
+-# DISABLED: cleanup_tasks.add_task(clean_python)
+-#
+-# -----------------------------------------------------------------------------
+-# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
+-# -----------------------------------------------------------------------------
+-def config_add_cleanup_dirs(directories):
+- # pylint: disable=protected-access
+- the_cleanup_directories = namespace._configuration["clean"]["directories"]
+- the_cleanup_directories.extend(directories)
+-
+-def config_add_cleanup_files(files):
+- # pylint: disable=protected-access
+- the_cleanup_files = namespace._configuration["clean"]["files"]
+- the_cleanup_files.extend(files)
+diff --git a/tasks/_vendor/README.rst b/tasks/_vendor/README.rst
+deleted file mode 100644
+index 68fc06a..0000000
+--- a/tasks/_vendor/README.rst
++++ /dev/null
+@@ -1,35 +0,0 @@
+-tasks/_vendor: Bundled vendor parts -- needed by tasks
+-===============================================================================
+-
+-This directory contains bundled archives that may be needed to run the tasks.
+-Especially, it contains an executable "invoke.zip" archive.
+-This archive can be used when invoke is not installed.
+-
+-To execute invoke from the bundled ZIP archive::
+-
+-
+- python -m tasks/_vendor/invoke.zip --help
+- python -m tasks/_vendor/invoke.zip --version
+-
+-
+-Example for a local "bin/invoke" script in a UNIX like platform environment::
+-
+- #!/bin/bash
+- # RUN INVOKE: From bundled ZIP file.
+-
+- HERE=$(dirname $0)
+-
+- python ${HERE}/../tasks/_vendor/invoke.zip $*
+-
+-Example for a local "bin/invoke.cmd" script in a Windows environment::
+-
+- @echo off
+- REM ==========================================================================
+- REM RUN INVOKE: From bundled ZIP file.
+- REM ==========================================================================
+-
+- setlocal
+- set HERE=%~dp0
+- if not defined PYTHON set PYTHON=python
+-
+- %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
+diff --git a/tasks/_vendor/invoke.zip b/tasks/_vendor/invoke.zip
+deleted file mode 100644
+index bd1941289b427b6cd6beaf188c85def58ee4ce33..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 172281
+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�X@2|f
+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
+zGGZqJEe%^irYHi$5Y*76Dh4>&<!%0AfH2K(?hj9A8%&MPJ$V+x5Uh_eLLl~WVHG$B
+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	oOmMl_1ASDTDHpw
+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
+z%0A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
+
+diff --git a/tasks/_vendor/path.py b/tasks/_vendor/path.py
+deleted file mode 100644
+index 2c7a71c..0000000
+--- a/tasks/_vendor/path.py
++++ /dev/null
+@@ -1,1725 +0,0 @@
+-#
+-# SOURCE: https://pypi.python.org/pypi/path.py
+-# VERSION: 8.2.1
+-# -----------------------------------------------------------------------------
+-# Copyright (c) 2010 Mikhail Gusarov
+-#
+-# 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.
+-#
+-
+-"""
+-path.py - An object representing a path to a file or directory.
+-
+-https://github.com/jaraco/path.py
+-
+-Example::
+-
+- from path import Path
+- d = Path('/home/guido/bin')
+- for f in d.files('*.py'):
+- f.chmod(0o755)
+-"""
+-
+-from __future__ import unicode_literals
+-
+-import sys
+-import warnings
+-import os
+-import fnmatch
+-import glob
+-import shutil
+-import codecs
+-import hashlib
+-import errno
+-import tempfile
+-import functools
+-import operator
+-import re
+-import contextlib
+-import io
+-from distutils import dir_util
+-import importlib
+-
+-try:
+- import win32security
+-except ImportError:
+- pass
+-
+-try:
+- import pwd
+-except ImportError:
+- pass
+-
+-try:
+- import grp
+-except ImportError:
+- pass
+-
+-##############################################################################
+-# Python 2/3 support
+-PY3 = sys.version_info >= (3,)
+-PY2 = not PY3
+-
+-string_types = str,
+-text_type = str
+-getcwdu = os.getcwd
+-
+-def surrogate_escape(error):
+- """
+- Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
+- """
+- chars = error.object[error.start:error.end]
+- assert len(chars) == 1
+- val = ord(chars)
+- val += 0xdc00
+- return __builtin__.unichr(val), error.end
+-
+-if PY2:
+- import __builtin__
+- string_types = __builtin__.basestring,
+- text_type = __builtin__.unicode
+- getcwdu = os.getcwdu
+- codecs.register_error('surrogateescape', surrogate_escape)
+-
+-@contextlib.contextmanager
+-def io_error_compat():
+- try:
+- yield
+- except IOError as io_err:
+- # On Python 2, io.open raises IOError; transform to OSError for
+- # future compatibility.
+- os_err = OSError(*io_err.args)
+- os_err.filename = getattr(io_err, 'filename', None)
+- raise os_err
+-
+-##############################################################################
+-
+-__all__ = ['Path', 'CaseInsensitivePattern']
+-
+-
+-LINESEPS = ['\r\n', '\r', '\n']
+-U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
+-NEWLINE = re.compile('|'.join(LINESEPS))
+-U_NEWLINE = re.compile('|'.join(U_LINESEPS))
+-NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
+-U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
+-
+-
+-try:
+- import pkg_resources
+- __version__ = pkg_resources.require('path.py')[0].version
+-except Exception:
+- __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
+-
+-
+-class TreeWalkWarning(Warning):
+- pass
+-
+-
+-# from jaraco.functools
+-def compose(*funcs):
+- compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
+- return functools.reduce(compose_two, funcs)
+-
+-
+-def simple_cache(func):
+- """
+- Save results for the :meth:'path.using_module' classmethod.
+- When Python 3.2 is available, use functools.lru_cache instead.
+- """
+- saved_results = {}
+-
+- def wrapper(cls, module):
+- if module in saved_results:
+- return saved_results[module]
+- saved_results[module] = func(cls, module)
+- return saved_results[module]
+- return wrapper
+-
+-
+-class ClassProperty(property):
+- def __get__(self, cls, owner):
+- return self.fget.__get__(None, owner)()
+-
+-
+-class multimethod(object):
+- """
+- Acts like a classmethod when invoked from the class and like an
+- instancemethod when invoked from the instance.
+- """
+- def __init__(self, func):
+- self.func = func
+-
+- def __get__(self, instance, owner):
+- return (
+- functools.partial(self.func, owner) if instance is None
+- else functools.partial(self.func, owner, instance)
+- )
+-
+-
+-class Path(text_type):
+- """
+- Represents a filesystem path.
+-
+- For documentation on individual methods, consult their
+- counterparts in :mod:`os.path`.
+-
+- Some methods are additionally included from :mod:`shutil`.
+- The functions are linked directly into the class namespace
+- such that they will be bound to the Path instance. For example,
+- ``Path(src).copy(target)`` is equivalent to
+- ``shutil.copy(src, target)``. Therefore, when referencing
+- the docs for these methods, assume `src` references `self`,
+- the Path instance.
+- """
+-
+- module = os.path
+- """ The path module to use for path operations.
+-
+- .. seealso:: :mod:`os.path`
+- """
+-
+- def __init__(self, other=''):
+- if other is None:
+- raise TypeError("Invalid initial value for path: None")
+-
+- @classmethod
+- @simple_cache
+- def using_module(cls, module):
+- subclass_name = cls.__name__ + '_' + module.__name__
+- if PY2:
+- subclass_name = str(subclass_name)
+- bases = (cls,)
+- ns = {'module': module}
+- return type(subclass_name, bases, ns)
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- What class should be used to construct new instances from this class
+- """
+- return cls
+-
+- @classmethod
+- def _always_unicode(cls, path):
+- """
+- Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
+- is a proper Unicode string.
+- """
+- if PY3 or isinstance(path, text_type):
+- return path
+- return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
+-
+- # --- Special Python methods.
+-
+- def __repr__(self):
+- return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
+-
+- # Adding a Path and a string yields a Path.
+- def __add__(self, more):
+- try:
+- return self._next_class(super(Path, self).__add__(more))
+- except TypeError: # Python bug
+- return NotImplemented
+-
+- def __radd__(self, other):
+- if not isinstance(other, string_types):
+- return NotImplemented
+- return self._next_class(other.__add__(self))
+-
+- # The / operator joins Paths.
+- def __div__(self, rel):
+- """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(self, rel))
+-
+- # Make the / operator work even when true division is enabled.
+- __truediv__ = __div__
+-
+- # The / operator joins Paths the other way around
+- def __rdiv__(self, rel):
+- """ fp.__rdiv__(rel) == rel / fp
+-
+- Join two path components, adding a separator character if
+- needed.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- return self._next_class(self.module.join(rel, self))
+-
+- # Make the / operator work even when true division is enabled.
+- __rtruediv__ = __rdiv__
+-
+- def __enter__(self):
+- self._old_dir = self.getcwd()
+- os.chdir(self)
+- return self
+-
+- def __exit__(self, *_):
+- os.chdir(self._old_dir)
+-
+- @classmethod
+- def getcwd(cls):
+- """ Return the current working directory as a path object.
+-
+- .. seealso:: :func:`os.getcwdu`
+- """
+- return cls(getcwdu())
+-
+- #
+- # --- Operations on Path strings.
+-
+- def abspath(self):
+- """ .. seealso:: :func:`os.path.abspath` """
+- return self._next_class(self.module.abspath(self))
+-
+- def normcase(self):
+- """ .. seealso:: :func:`os.path.normcase` """
+- return self._next_class(self.module.normcase(self))
+-
+- def normpath(self):
+- """ .. seealso:: :func:`os.path.normpath` """
+- return self._next_class(self.module.normpath(self))
+-
+- def realpath(self):
+- """ .. seealso:: :func:`os.path.realpath` """
+- return self._next_class(self.module.realpath(self))
+-
+- def expanduser(self):
+- """ .. seealso:: :func:`os.path.expanduser` """
+- return self._next_class(self.module.expanduser(self))
+-
+- def expandvars(self):
+- """ .. seealso:: :func:`os.path.expandvars` """
+- return self._next_class(self.module.expandvars(self))
+-
+- def dirname(self):
+- """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
+- return self._next_class(self.module.dirname(self))
+-
+- def basename(self):
+- """ .. seealso:: :attr:`name`, :func:`os.path.basename` """
+- return self._next_class(self.module.basename(self))
+-
+- def expand(self):
+- """ Clean up a filename by calling :meth:`expandvars()`,
+- :meth:`expanduser()`, and :meth:`normpath()` on it.
+-
+- This is commonly everything needed to clean up a filename
+- read from a configuration file, for example.
+- """
+- return self.expandvars().expanduser().normpath()
+-
+- @property
+- def namebase(self):
+- """ The same as :meth:`name`, but with one file extension stripped off.
+-
+- For example,
+- ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
+- but
+- ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
+- """
+- base, ext = self.module.splitext(self.name)
+- return base
+-
+- @property
+- def ext(self):
+- """ The file extension, for example ``'.py'``. """
+- f, ext = self.module.splitext(self)
+- return ext
+-
+- @property
+- def drive(self):
+- """ The drive specifier, for example ``'C:'``.
+-
+- This is always empty on systems that don't use drive specifiers.
+- """
+- drive, r = self.module.splitdrive(self)
+- return self._next_class(drive)
+-
+- parent = property(
+- dirname, None, None,
+- """ This path's parent directory, as a new Path object.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').parent ==
+- Path('/usr/local/lib')``
+-
+- .. seealso:: :meth:`dirname`, :func:`os.path.dirname`
+- """)
+-
+- name = property(
+- basename, None, None,
+- """ The name of this file or directory without the full path.
+-
+- For example,
+- ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
+-
+- .. seealso:: :meth:`basename`, :func:`os.path.basename`
+- """)
+-
+- def splitpath(self):
+- """ p.splitpath() -> Return ``(p.parent, p.name)``.
+-
+- .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
+- """
+- parent, child = self.module.split(self)
+- return self._next_class(parent), child
+-
+- def splitdrive(self):
+- """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
+-
+- Split the drive specifier from this path. If there is
+- no drive specifier, :samp:`{p.drive}` is empty, so the return value
+- is simply ``(Path(''), p)``. This is always the case on Unix.
+-
+- .. seealso:: :func:`os.path.splitdrive`
+- """
+- drive, rel = self.module.splitdrive(self)
+- return self._next_class(drive), rel
+-
+- def splitext(self):
+- """ p.splitext() -> Return ``(p.stripext(), p.ext)``.
+-
+- Split the filename extension from this path and return
+- the two parts. Either part may be empty.
+-
+- The extension is everything from ``'.'`` to the end of the
+- last path segment. This has the property that if
+- ``(a, b) == p.splitext()``, then ``a + b == p``.
+-
+- .. seealso:: :func:`os.path.splitext`
+- """
+- filename, ext = self.module.splitext(self)
+- return self._next_class(filename), ext
+-
+- def stripext(self):
+- """ p.stripext() -> Remove one file extension from the path.
+-
+- For example, ``Path('/home/guido/python.tar.gz').stripext()``
+- returns ``Path('/home/guido/python.tar')``.
+- """
+- return self.splitext()[0]
+-
+- def splitunc(self):
+- """ .. seealso:: :func:`os.path.splitunc` """
+- unc, rest = self.module.splitunc(self)
+- return self._next_class(unc), rest
+-
+- @property
+- def uncshare(self):
+- """
+- The UNC mount point for this path.
+- This is empty for paths on local drives.
+- """
+- unc, r = self.module.splitunc(self)
+- return self._next_class(unc)
+-
+- @multimethod
+- def joinpath(cls, first, *others):
+- """
+- Join first to zero or more :class:`Path` components, adding a separator
+- character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
+- :samp:`{first}._next_class`.
+-
+- .. seealso:: :func:`os.path.join`
+- """
+- if not isinstance(first, cls):
+- first = cls(first)
+- return first._next_class(first.module.join(first, *others))
+-
+- def splitall(self):
+- r""" Return a list of the path components in this path.
+-
+- The first item in the list will be a Path. Its value will be
+- either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
+- directory of this path (for example, ``'/'`` or ``'C:\\'``). The
+- other items in the list will be strings.
+-
+- ``path.Path.joinpath(*result)`` will yield the original path.
+- """
+- parts = []
+- loc = self
+- while loc != os.curdir and loc != os.pardir:
+- prev = loc
+- loc, child = prev.splitpath()
+- if loc == prev:
+- break
+- parts.append(child)
+- parts.append(loc)
+- parts.reverse()
+- return parts
+-
+- def relpath(self, start='.'):
+- """ Return this path as a relative path,
+- based from `start`, which defaults to the current working directory.
+- """
+- cwd = self._next_class(start)
+- return cwd.relpathto(self)
+-
+- def relpathto(self, dest):
+- """ Return a relative path from `self` to `dest`.
+-
+- If there is no relative path from `self` to `dest`, for example if
+- they reside on different drives in Windows, then this returns
+- ``dest.abspath()``.
+- """
+- origin = self.abspath()
+- dest = self._next_class(dest).abspath()
+-
+- orig_list = origin.normcase().splitall()
+- # Don't normcase dest! We want to preserve the case.
+- dest_list = dest.splitall()
+-
+- if orig_list[0] != self.module.normcase(dest_list[0]):
+- # Can't get here from there.
+- return dest
+-
+- # Find the location where the two paths start to differ.
+- i = 0
+- for start_seg, dest_seg in zip(orig_list, dest_list):
+- if start_seg != self.module.normcase(dest_seg):
+- break
+- i += 1
+-
+- # Now i is the point where the two paths diverge.
+- # Need a certain number of "os.pardir"s to work up
+- # from the origin to the point of divergence.
+- segments = [os.pardir] * (len(orig_list) - i)
+- # Need to add the diverging part of dest_list.
+- segments += dest_list[i:]
+- if len(segments) == 0:
+- # If they happen to be identical, use os.curdir.
+- relpath = os.curdir
+- else:
+- relpath = self.module.join(*segments)
+- return self._next_class(relpath)
+-
+- # --- Listing, searching, walking, and matching
+-
+- def listdir(self, pattern=None):
+- """ D.listdir() -> List of items in this directory.
+-
+- Use :meth:`files` or :meth:`dirs` instead if you want a listing
+- of just files or just subdirectories.
+-
+- The elements of the list are Path objects.
+-
+- With the optional `pattern` argument, this only lists
+- items whose names match the given pattern.
+-
+- .. seealso:: :meth:`files`, :meth:`dirs`
+- """
+- if pattern is None:
+- pattern = '*'
+- return [
+- self / child
+- for child in map(self._always_unicode, os.listdir(self))
+- if self._next_class(child).fnmatch(pattern)
+- ]
+-
+- def dirs(self, pattern=None):
+- """ D.dirs() -> List of this directory's subdirectories.
+-
+- The elements of the list are Path objects.
+- This does not walk recursively into subdirectories
+- (but see :meth:`walkdirs`).
+-
+- With the optional `pattern` argument, this only lists
+- directories whose names match the given pattern. For
+- example, ``d.dirs('build-*')``.
+- """
+- return [p for p in self.listdir(pattern) if p.isdir()]
+-
+- def files(self, pattern=None):
+- """ D.files() -> List of the files in this directory.
+-
+- The elements of the list are Path objects.
+- This does not walk into subdirectories (see :meth:`walkfiles`).
+-
+- With the optional `pattern` argument, this only lists files
+- whose names match the given pattern. For example,
+- ``d.files('*.pyc')``.
+- """
+-
+- return [p for p in self.listdir(pattern) if p.isfile()]
+-
+- def walk(self, pattern=None, errors='strict'):
+- """ D.walk() -> iterator over files and subdirs, recursively.
+-
+- The iterator yields Path objects naming each child item of
+- this directory and its descendants. This requires that
+- ``D.isdir()``.
+-
+- This performs a depth-first traversal of the directory tree.
+- Each directory is returned just before all its children.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. Other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- `errors` may also be an arbitrary callable taking a msg parameter.
+- """
+- class Handlers:
+- def strict(msg):
+- raise
+-
+- def warn(msg):
+- warnings.warn(msg, TreeWalkWarning)
+-
+- def ignore(msg):
+- pass
+-
+- if not callable(errors) and errors not in vars(Handlers):
+- raise ValueError("invalid errors parameter")
+- errors = vars(Handlers).get(errors, errors)
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to list directory '%(self)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- return
+-
+- for child in childList:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- try:
+- isdir = child.isdir()
+- except Exception:
+- exc = sys.exc_info()[1]
+- tmpl = "Unable to access '%(child)s': %(exc)s"
+- msg = tmpl % locals()
+- errors(msg)
+- isdir = False
+-
+- if isdir:
+- for item in child.walk(pattern, errors):
+- yield item
+-
+- def walkdirs(self, pattern=None, errors='strict'):
+- """ D.walkdirs() -> iterator over subdirs, recursively.
+-
+- With the optional `pattern` argument, this yields only
+- directories whose names match the given pattern. For
+- example, ``mydir.walkdirs('*test')`` yields only directories
+- with names ending in ``'test'``.
+-
+- The `errors=` keyword argument controls behavior when an
+- error occurs. The default is ``'strict'``, which causes an
+- exception. The other allowed values are ``'warn'`` (which
+- reports the error via :func:`warnings.warn()`), and ``'ignore'``.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- dirs = self.dirs()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in dirs:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- for subsubdir in child.walkdirs(pattern, errors):
+- yield subsubdir
+-
+- def walkfiles(self, pattern=None, errors='strict'):
+- """ D.walkfiles() -> iterator over files in D, recursively.
+-
+- The optional argument `pattern` limits the results to files
+- with names that match the pattern. For example,
+- ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
+- extension.
+- """
+- if errors not in ('strict', 'warn', 'ignore'):
+- raise ValueError("invalid errors parameter")
+-
+- try:
+- childList = self.listdir()
+- except Exception:
+- if errors == 'ignore':
+- return
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to list directory '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- return
+- else:
+- raise
+-
+- for child in childList:
+- try:
+- isfile = child.isfile()
+- isdir = not isfile and child.isdir()
+- except:
+- if errors == 'ignore':
+- continue
+- elif errors == 'warn':
+- warnings.warn(
+- "Unable to access '%s': %s"
+- % (self, sys.exc_info()[1]),
+- TreeWalkWarning)
+- continue
+- else:
+- raise
+-
+- if isfile:
+- if pattern is None or child.fnmatch(pattern):
+- yield child
+- elif isdir:
+- for f in child.walkfiles(pattern, errors):
+- yield f
+-
+- def fnmatch(self, pattern, normcase=None):
+- """ Return ``True`` if `self.name` matches the given `pattern`.
+-
+- `pattern` - A filename pattern with wildcards,
+- for example ``'*.py'``. If the pattern contains a `normcase`
+- attribute, it is applied to the name and path prior to comparison.
+-
+- `normcase` - (optional) A function used to normalize the pattern and
+- filename before matching. Defaults to :meth:`self.module`, which defaults
+- to :meth:`os.path.normcase`.
+-
+- .. seealso:: :func:`fnmatch.fnmatch`
+- """
+- default_normcase = getattr(pattern, 'normcase', self.module.normcase)
+- normcase = normcase or default_normcase
+- name = normcase(self.name)
+- pattern = normcase(pattern)
+- return fnmatch.fnmatchcase(name, pattern)
+-
+- def glob(self, pattern):
+- """ Return a list of Path objects that match the pattern.
+-
+- `pattern` - a path relative to this directory, with wildcards.
+-
+- For example, ``Path('/users').glob('*/bin/*')`` returns a list
+- of all the files users have in their :file:`bin` directories.
+-
+- .. seealso:: :func:`glob.glob`
+- """
+- cls = self._next_class
+- return [cls(s) for s in glob.glob(self / pattern)]
+-
+- #
+- # --- Reading or writing an entire file at once.
+-
+- def open(self, *args, **kwargs):
+- """ Open this file and return a corresponding :class:`file` object.
+-
+- Keyword arguments work as in :func:`io.open`. If the file cannot be
+- opened, an :class:`~exceptions.OSError` is raised.
+- """
+- with io_error_compat():
+- return io.open(self, *args, **kwargs)
+-
+- def bytes(self):
+- """ Open this file, read all bytes, return them as a string. """
+- with self.open('rb') as f:
+- return f.read()
+-
+- def chunks(self, size, *args, **kwargs):
+- """ Returns a generator yielding chunks of the file, so it can
+- be read piece by piece with a simple for loop.
+-
+- Any argument you pass after `size` will be passed to :meth:`open`.
+-
+- :example:
+-
+- >>> hash = hashlib.md5()
+- >>> for chunk in Path("path.py").chunks(8192, mode='rb'):
+- ... hash.update(chunk)
+-
+- This will read the file by chunks of 8192 bytes.
+- """
+- with self.open(*args, **kwargs) as f:
+- for chunk in iter(lambda: f.read(size) or None, None):
+- yield chunk
+-
+- def write_bytes(self, bytes, append=False):
+- """ Open this file and write the given bytes to it.
+-
+- Default behavior is to overwrite any existing file.
+- Call ``p.write_bytes(bytes, append=True)`` to append instead.
+- """
+- if append:
+- mode = 'ab'
+- else:
+- mode = 'wb'
+- with self.open(mode) as f:
+- f.write(bytes)
+-
+- def text(self, encoding=None, errors='strict'):
+- r""" Open this file, read it in, return the content as a string.
+-
+- All newline sequences are converted to ``'\n'``. Keyword arguments
+- will be passed to :meth:`open`.
+-
+- .. seealso:: :meth:`lines`
+- """
+- with self.open(mode='r', encoding=encoding, errors=errors) as f:
+- return U_NEWLINE.sub('\n', f.read())
+-
+- def write_text(self, text, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given text to this file.
+-
+- The default behavior is to overwrite any existing file;
+- to append instead, use the `append=True` keyword argument.
+-
+- There are two differences between :meth:`write_text` and
+- :meth:`write_bytes`: newline handling and Unicode handling.
+- See below.
+-
+- Parameters:
+-
+- `text` - str/unicode - The text to be written.
+-
+- `encoding` - str - The Unicode encoding that will be used.
+- This is ignored if `text` isn't a Unicode string.
+-
+- `errors` - str - How to handle Unicode encoding errors.
+- Default is ``'strict'``. See ``help(unicode.encode)`` for the
+- options. This is ignored if `text` isn't a Unicode
+- string.
+-
+- `linesep` - keyword argument - str/unicode - The sequence of
+- characters to be used to mark end-of-line. The default is
+- :data:`os.linesep`. You can also specify ``None`` to
+- leave all newlines as they are in `text`.
+-
+- `append` - keyword argument - bool - Specifies what to do if
+- the file already exists (``True``: append to the end of it;
+- ``False``: overwrite it.) The default is ``False``.
+-
+-
+- --- Newline handling.
+-
+- ``write_text()`` converts all standard end-of-line sequences
+- (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
+- end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
+- the end-of-line marker is ``'\r\n'``).
+-
+- If you don't like your platform's default, you can override it
+- using the `linesep=` keyword argument. If you specifically want
+- ``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
+-
+- This applies to Unicode text the same as to 8-bit text, except
+- there are three additional standard Unicode end-of-line sequences:
+- ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
+-
+- (This is slightly different from when you open a file for
+- writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
+- in Python.)
+-
+-
+- --- Unicode
+-
+- If `text` isn't Unicode, then apart from newline handling, the
+- bytes are written verbatim to the file. The `encoding` and
+- `errors` arguments are not used and must be omitted.
+-
+- If `text` is Unicode, it is first converted to :func:`bytes` using the
+- specified `encoding` (or the default encoding if `encoding`
+- isn't specified). The `errors` argument applies only to this
+- conversion.
+-
+- """
+- if isinstance(text, text_type):
+- if linesep is not None:
+- text = U_NEWLINE.sub(linesep, text)
+- text = text.encode(encoding or sys.getdefaultencoding(), errors)
+- else:
+- assert encoding is None
+- text = NEWLINE.sub(linesep, text)
+- self.write_bytes(text, append=append)
+-
+- def lines(self, encoding=None, errors='strict', retain=True):
+- r""" Open this file, read all lines, return them in a list.
+-
+- Optional arguments:
+- `encoding` - The Unicode encoding (or character set) of
+- the file. The default is ``None``, meaning the content
+- of the file is read as 8-bit characters and returned
+- as a list of (non-Unicode) str objects.
+- `errors` - How to handle Unicode errors; see help(str.decode)
+- for the options. Default is ``'strict'``.
+- `retain` - If ``True``, retain newline characters; but all newline
+- character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
+- translated to ``'\n'``. If ``False``, newline characters are
+- stripped off. Default is ``True``.
+-
+- This uses ``'U'`` mode.
+-
+- .. seealso:: :meth:`text`
+- """
+- if encoding is None and retain:
+- with self.open('U') as f:
+- return f.readlines()
+- else:
+- return self.text(encoding, errors).splitlines(retain)
+-
+- def write_lines(self, lines, encoding=None, errors='strict',
+- linesep=os.linesep, append=False):
+- r""" Write the given lines of text to this file.
+-
+- By default this overwrites any existing file at this path.
+-
+- This puts a platform-specific newline sequence on every line.
+- See `linesep` below.
+-
+- `lines` - A list of strings.
+-
+- `encoding` - A Unicode encoding to use. This applies only if
+- `lines` contains any Unicode strings.
+-
+- `errors` - How to handle errors in Unicode encoding. This
+- also applies only to Unicode strings.
+-
+- linesep - The desired line-ending. This line-ending is
+- applied to every line. If a line already has any
+- standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
+- ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
+- be stripped off and this will be used instead. The
+- default is os.linesep, which is platform-dependent
+- (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
+- Specify ``None`` to write the lines as-is, like
+- :meth:`file.writelines`.
+-
+- Use the keyword argument ``append=True`` to append lines to the
+- file. The default is to overwrite the file.
+-
+- .. warning ::
+-
+- When you use this with Unicode data, if the encoding of the
+- existing data in the file is different from the encoding
+- you specify with the `encoding=` parameter, the result is
+- mixed-encoding data, which can really confuse someone trying
+- to read the file later.
+- """
+- with self.open('ab' if append else 'wb') as f:
+- for l in lines:
+- isUnicode = isinstance(l, text_type)
+- if linesep is not None:
+- pattern = U_NL_END if isUnicode else NL_END
+- l = pattern.sub('', l) + linesep
+- if isUnicode:
+- l = l.encode(encoding or sys.getdefaultencoding(), errors)
+- f.write(l)
+-
+- def read_md5(self):
+- """ Calculate the md5 hash for this file.
+-
+- This reads through the entire file.
+-
+- .. seealso:: :meth:`read_hash`
+- """
+- return self.read_hash('md5')
+-
+- def _hash(self, hash_name):
+- """ Returns a hash object for the file at the current path.
+-
+- `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
+- that's available in the :mod:`hashlib` module.
+- """
+- m = hashlib.new(hash_name)
+- for chunk in self.chunks(8192, mode="rb"):
+- m.update(chunk)
+- return m
+-
+- def read_hash(self, hash_name):
+- """ Calculate given hash for this file.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.digest`
+- """
+- return self._hash(hash_name).digest()
+-
+- def read_hexhash(self, hash_name):
+- """ Calculate given hash for this file, returning hexdigest.
+-
+- List of supported hashes can be obtained from :mod:`hashlib` package.
+- This reads the entire file.
+-
+- .. seealso:: :meth:`hashlib.hash.hexdigest`
+- """
+- return self._hash(hash_name).hexdigest()
+-
+- # --- Methods for querying the filesystem.
+- # N.B. On some platforms, the os.path functions may be implemented in C
+- # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
+- # bound. Playing it safe and wrapping them all in method calls.
+-
+- def isabs(self):
+- """ .. seealso:: :func:`os.path.isabs` """
+- return self.module.isabs(self)
+-
+- def exists(self):
+- """ .. seealso:: :func:`os.path.exists` """
+- return self.module.exists(self)
+-
+- def isdir(self):
+- """ .. seealso:: :func:`os.path.isdir` """
+- return self.module.isdir(self)
+-
+- def isfile(self):
+- """ .. seealso:: :func:`os.path.isfile` """
+- return self.module.isfile(self)
+-
+- def islink(self):
+- """ .. seealso:: :func:`os.path.islink` """
+- return self.module.islink(self)
+-
+- def ismount(self):
+- """ .. seealso:: :func:`os.path.ismount` """
+- return self.module.ismount(self)
+-
+- def samefile(self, other):
+- """ .. seealso:: :func:`os.path.samefile` """
+- if not hasattr(self.module, 'samefile'):
+- other = Path(other).realpath().normpath().normcase()
+- return self.realpath().normpath().normcase() == other
+- return self.module.samefile(self, other)
+-
+- def getatime(self):
+- """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
+- return self.module.getatime(self)
+-
+- atime = property(
+- getatime, None, None,
+- """ Last access time of the file.
+-
+- .. seealso:: :meth:`getatime`, :func:`os.path.getatime`
+- """)
+-
+- def getmtime(self):
+- """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
+- return self.module.getmtime(self)
+-
+- mtime = property(
+- getmtime, None, None,
+- """ Last-modified time of the file.
+-
+- .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
+- """)
+-
+- def getctime(self):
+- """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
+- return self.module.getctime(self)
+-
+- ctime = property(
+- getctime, None, None,
+- """ Creation time of the file.
+-
+- .. seealso:: :meth:`getctime`, :func:`os.path.getctime`
+- """)
+-
+- def getsize(self):
+- """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """
+- return self.module.getsize(self)
+-
+- size = property(
+- getsize, None, None,
+- """ Size of the file, in bytes.
+-
+- .. seealso:: :meth:`getsize`, :func:`os.path.getsize`
+- """)
+-
+- if hasattr(os, 'access'):
+- def access(self, mode):
+- """ Return ``True`` if current user has access to this path.
+-
+- mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
+- :data:`os.W_OK`, :data:`os.X_OK`
+-
+- .. seealso:: :func:`os.access`
+- """
+- return os.access(self, mode)
+-
+- def stat(self):
+- """ Perform a ``stat()`` system call on this path.
+-
+- .. seealso:: :meth:`lstat`, :func:`os.stat`
+- """
+- return os.stat(self)
+-
+- def lstat(self):
+- """ Like :meth:`stat`, but do not follow symbolic links.
+-
+- .. seealso:: :meth:`stat`, :func:`os.lstat`
+- """
+- return os.lstat(self)
+-
+- def __get_owner_windows(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
+-
+- .. seealso:: :attr:`owner`
+- """
+- desc = win32security.GetFileSecurity(
+- self, win32security.OWNER_SECURITY_INFORMATION)
+- sid = desc.GetSecurityDescriptorOwner()
+- account, domain, typecode = win32security.LookupAccountSid(None, sid)
+- return domain + '\\' + account
+-
+- def __get_owner_unix(self):
+- """
+- Return the name of the owner of this file or directory. Follow
+- symbolic links.
+-
+- .. seealso:: :attr:`owner`
+- """
+- st = self.stat()
+- return pwd.getpwuid(st.st_uid).pw_name
+-
+- def __get_owner_not_implemented(self):
+- raise NotImplementedError("Ownership not available on this platform.")
+-
+- if 'win32security' in globals():
+- get_owner = __get_owner_windows
+- elif 'pwd' in globals():
+- get_owner = __get_owner_unix
+- else:
+- get_owner = __get_owner_not_implemented
+-
+- owner = property(
+- get_owner, None, None,
+- """ Name of the owner of this file or directory.
+-
+- .. seealso:: :meth:`get_owner`""")
+-
+- if hasattr(os, 'statvfs'):
+- def statvfs(self):
+- """ Perform a ``statvfs()`` system call on this path.
+-
+- .. seealso:: :func:`os.statvfs`
+- """
+- return os.statvfs(self)
+-
+- if hasattr(os, 'pathconf'):
+- def pathconf(self, name):
+- """ .. seealso:: :func:`os.pathconf` """
+- return os.pathconf(self, name)
+-
+- #
+- # --- Modifying operations on files and directories
+-
+- def utime(self, times):
+- """ Set the access and modified times of this file.
+-
+- .. seealso:: :func:`os.utime`
+- """
+- os.utime(self, times)
+- return self
+-
+- def chmod(self, mode):
+- """
+- Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
+- mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
+-
+- .. seealso:: :func:`os.chmod`
+- """
+- if isinstance(mode, string_types):
+- mask = _multi_permission_mask(mode)
+- mode = mask(self.stat().st_mode)
+- os.chmod(self, mode)
+- return self
+-
+- def chown(self, uid=-1, gid=-1):
+- """
+- Change the owner and group by names rather than the uid or gid numbers.
+-
+- .. seealso:: :func:`os.chown`
+- """
+- if hasattr(os, 'chown'):
+- if 'pwd' in globals() and isinstance(uid, string_types):
+- uid = pwd.getpwnam(uid).pw_uid
+- if 'grp' in globals() and isinstance(gid, string_types):
+- gid = grp.getgrnam(gid).gr_gid
+- os.chown(self, uid, gid)
+- else:
+- raise NotImplementedError("Ownership not available on this platform.")
+- return self
+-
+- def rename(self, new):
+- """ .. seealso:: :func:`os.rename` """
+- os.rename(self, new)
+- return self._next_class(new)
+-
+- def renames(self, new):
+- """ .. seealso:: :func:`os.renames` """
+- os.renames(self, new)
+- return self._next_class(new)
+-
+- #
+- # --- Create/delete operations on directories
+-
+- def mkdir(self, mode=0o777):
+- """ .. seealso:: :func:`os.mkdir` """
+- os.mkdir(self, mode)
+- return self
+-
+- def mkdir_p(self, mode=0o777):
+- """ Like :meth:`mkdir`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.mkdir(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def makedirs(self, mode=0o777):
+- """ .. seealso:: :func:`os.makedirs` """
+- os.makedirs(self, mode)
+- return self
+-
+- def makedirs_p(self, mode=0o777):
+- """ Like :meth:`makedirs`, but does not raise an exception if the
+- directory already exists. """
+- try:
+- self.makedirs(mode)
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def rmdir(self):
+- """ .. seealso:: :func:`os.rmdir` """
+- os.rmdir(self)
+- return self
+-
+- def rmdir_p(self):
+- """ Like :meth:`rmdir`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.rmdir()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- def removedirs(self):
+- """ .. seealso:: :func:`os.removedirs` """
+- os.removedirs(self)
+- return self
+-
+- def removedirs_p(self):
+- """ Like :meth:`removedirs`, but does not raise an exception if the
+- directory is not empty or does not exist. """
+- try:
+- self.removedirs()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
+- raise
+- return self
+-
+- # --- Modifying operations on files
+-
+- def touch(self):
+- """ Set the access/modified times of this file to the current time.
+- Create the file if it does not exist.
+- """
+- fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
+- os.close(fd)
+- os.utime(self, None)
+- return self
+-
+- def remove(self):
+- """ .. seealso:: :func:`os.remove` """
+- os.remove(self)
+- return self
+-
+- def remove_p(self):
+- """ Like :meth:`remove`, but does not raise an exception if the
+- file does not exist. """
+- try:
+- self.unlink()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def unlink(self):
+- """ .. seealso:: :func:`os.unlink` """
+- os.unlink(self)
+- return self
+-
+- def unlink_p(self):
+- """ Like :meth:`unlink`, but does not raise an exception if the
+- file does not exist. """
+- self.remove_p()
+- return self
+-
+- # --- Links
+-
+- if hasattr(os, 'link'):
+- def link(self, newpath):
+- """ Create a hard link at `newpath`, pointing to this file.
+-
+- .. seealso:: :func:`os.link`
+- """
+- os.link(self, newpath)
+- return self._next_class(newpath)
+-
+- if hasattr(os, 'symlink'):
+- def symlink(self, newlink):
+- """ Create a symbolic link at `newlink`, pointing here.
+-
+- .. seealso:: :func:`os.symlink`
+- """
+- os.symlink(self, newlink)
+- return self._next_class(newlink)
+-
+- if hasattr(os, 'readlink'):
+- def readlink(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result may be an absolute or a relative path.
+-
+- .. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
+- """
+- return self._next_class(os.readlink(self))
+-
+- def readlinkabs(self):
+- """ Return the path to which this symbolic link points.
+-
+- The result is always an absolute path.
+-
+- .. seealso:: :meth:`readlink`, :func:`os.readlink`
+- """
+- p = self.readlink()
+- if p.isabs():
+- return p
+- else:
+- return (self.parent / p).abspath()
+-
+- # High-level functions from shutil
+- # These functions will be bound to the instance such that
+- # Path(name).copy(target) will invoke shutil.copy(name, target)
+-
+- copyfile = shutil.copyfile
+- copymode = shutil.copymode
+- copystat = shutil.copystat
+- copy = shutil.copy
+- copy2 = shutil.copy2
+- copytree = shutil.copytree
+- if hasattr(shutil, 'move'):
+- move = shutil.move
+- rmtree = shutil.rmtree
+-
+- def rmtree_p(self):
+- """ Like :meth:`rmtree`, but does not raise an exception if the
+- directory does not exist. """
+- try:
+- self.rmtree()
+- except OSError:
+- _, e, _ = sys.exc_info()
+- if e.errno != errno.ENOENT:
+- raise
+- return self
+-
+- def chdir(self):
+- """ .. seealso:: :func:`os.chdir` """
+- os.chdir(self)
+-
+- cd = chdir
+-
+- def merge_tree(self, dst, symlinks=False, *args, **kwargs):
+- """
+- Copy entire contents of self to dst, overwriting existing
+- contents in dst with those in self.
+-
+- If the additional keyword `update` is True, each
+- `src` will only be copied if `dst` does not exist,
+- or `src` is newer than `dst`.
+-
+- Note that the technique employed stages the files in a temporary
+- directory first, so this function is not suitable for merging
+- trees with large files, especially if the temporary directory
+- is not capable of storing a copy of the entire source tree.
+- """
+- update = kwargs.pop('update', False)
+- with tempdir() as _temp_dir:
+- # first copy the tree to a stage directory to support
+- # the parameters and behavior of copytree.
+- stage = _temp_dir / str(hash(self))
+- self.copytree(stage, symlinks, *args, **kwargs)
+- # now copy everything from the stage directory using
+- # the semantics of dir_util.copy_tree
+- dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
+- update=update)
+-
+- #
+- # --- Special stuff from os
+-
+- if hasattr(os, 'chroot'):
+- def chroot(self):
+- """ .. seealso:: :func:`os.chroot` """
+- os.chroot(self)
+-
+- if hasattr(os, 'startfile'):
+- def startfile(self):
+- """ .. seealso:: :func:`os.startfile` """
+- os.startfile(self)
+- return self
+-
+- # in-place re-writing, courtesy of Martijn Pieters
+- # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
+- @contextlib.contextmanager
+- def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
+- newline=None, backup_extension=None):
+- """
+- A context in which a file may be re-written in-place with new content.
+-
+- Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
+- replaces `readable`.
+-
+- If an exception occurs, the old file is restored, removing the
+- written data.
+-
+- Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
+- allowed. A :exc:`ValueError` is raised on invalid modes.
+-
+- For example, to add line numbers to a file::
+-
+- p = Path(filename)
+- assert p.isfile()
+- with p.in_place() as (reader, writer):
+- for number, line in enumerate(reader, 1):
+- writer.write('{0:3}: '.format(number)))
+- writer.write(line)
+-
+- Thereafter, the file at `filename` will have line numbers in it.
+- """
+- import io
+-
+- if set(mode).intersection('wa+'):
+- raise ValueError('Only read-only file modes can be used')
+-
+- # move existing file to backup, create new file with same permissions
+- # borrowed extensively from the fileinput module
+- backup_fn = self + (backup_extension or os.extsep + 'bak')
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+- os.rename(self, backup_fn)
+- readable = io.open(backup_fn, mode, buffering=buffering,
+- encoding=encoding, errors=errors, newline=newline)
+- try:
+- perm = os.fstat(readable.fileno()).st_mode
+- except OSError:
+- writable = open(self, 'w' + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- else:
+- os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
+- if hasattr(os, 'O_BINARY'):
+- os_mode |= os.O_BINARY
+- fd = os.open(self, os_mode, perm)
+- writable = io.open(fd, "w" + mode.replace('r', ''),
+- buffering=buffering, encoding=encoding, errors=errors,
+- newline=newline)
+- try:
+- if hasattr(os, 'chmod'):
+- os.chmod(self, perm)
+- except OSError:
+- pass
+- try:
+- yield readable, writable
+- except Exception:
+- # move backup back
+- readable.close()
+- writable.close()
+- try:
+- os.unlink(self)
+- except os.error:
+- pass
+- os.rename(backup_fn, self)
+- raise
+- else:
+- readable.close()
+- writable.close()
+- finally:
+- try:
+- os.unlink(backup_fn)
+- except os.error:
+- pass
+-
+- @ClassProperty
+- @classmethod
+- def special(cls):
+- """
+- Return a SpecialResolver object suitable referencing a suitable
+- directory for the relevant platform for the given
+- type of content.
+-
+- For example, to get a user config directory, invoke:
+-
+- dir = Path.special().user.config
+-
+- Uses the `appdirs
+- <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
+- the paths in a platform-friendly way.
+-
+- To create a config directory for 'My App', consider:
+-
+- dir = Path.special("My App").user.config.makedirs_p()
+-
+- If the ``appdirs`` module is not installed, invocation
+- of special will raise an ImportError.
+- """
+- return functools.partial(SpecialResolver, cls)
+-
+-
+-class SpecialResolver(object):
+- class ResolverScope:
+- def __init__(self, paths, scope):
+- self.paths = paths
+- self.scope = scope
+-
+- def __getattr__(self, class_):
+- return self.paths.get_dir(self.scope, class_)
+-
+- def __init__(self, path_class, *args, **kwargs):
+- appdirs = importlib.import_module('appdirs')
+-
+- # let appname default to None until
+- # https://github.com/ActiveState/appdirs/issues/55 is solved.
+- not args and kwargs.setdefault('appname', None)
+-
+- vars(self).update(
+- path_class=path_class,
+- wrapper=appdirs.AppDirs(*args, **kwargs),
+- )
+-
+- def __getattr__(self, scope):
+- return self.ResolverScope(self, scope)
+-
+- def get_dir(self, scope, class_):
+- """
+- Return the callable function from appdirs, but with the
+- result wrapped in self.path_class
+- """
+- prop_name = '{scope}_{class_}_dir'.format(**locals())
+- value = getattr(self.wrapper, prop_name)
+- MultiPath = Multi.for_class(self.path_class)
+- return MultiPath.detect(value)
+-
+-
+-class Multi:
+- """
+- A mix-in for a Path which may contain multiple Path separated by pathsep.
+- """
+- @classmethod
+- def for_class(cls, path_cls):
+- name = 'Multi' + path_cls.__name__
+- if PY2:
+- name = str(name)
+- return type(name, (cls, path_cls), {})
+-
+- @classmethod
+- def detect(cls, input):
+- if os.pathsep not in input:
+- cls = cls._next_class
+- return cls(input)
+-
+- def __iter__(self):
+- return iter(map(self._next_class, self.split(os.pathsep)))
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- """
+- Multi-subclasses should use the parent class
+- """
+- return next(
+- class_
+- for class_ in cls.__mro__
+- if not issubclass(class_, Multi)
+- )
+-
+-
+-class tempdir(Path):
+- """
+- A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
+- same parameters that you can use as a context manager.
+-
+- Example:
+-
+- with tempdir() as d:
+- # do stuff with the Path object "d"
+-
+- # here the directory is deleted automatically
+-
+- .. seealso:: :func:`tempfile.mkdtemp`
+- """
+-
+- @ClassProperty
+- @classmethod
+- def _next_class(cls):
+- return Path
+-
+- def __new__(cls, *args, **kwargs):
+- dirname = tempfile.mkdtemp(*args, **kwargs)
+- return super(tempdir, cls).__new__(cls, dirname)
+-
+- def __init__(self, *args, **kwargs):
+- pass
+-
+- def __enter__(self):
+- return self
+-
+- def __exit__(self, exc_type, exc_value, traceback):
+- if not exc_value:
+- self.rmtree()
+-
+-
+-def _multi_permission_mask(mode):
+- """
+- Support multiple, comma-separated Unix chmod symbolic modes.
+-
+- >>> _multi_permission_mask('a=r,u+w')(0) == 0o644
+- True
+- """
+- compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
+- return functools.reduce(compose, map(_permission_mask, mode.split(',')))
+-
+-
+-def _permission_mask(mode):
+- """
+- Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
+- suitable for applying to a mask to affect that change.
+-
+- >>> mask = _permission_mask('ugo+rwx')
+- >>> mask(0o554) == 0o777
+- True
+-
+- >>> _permission_mask('go-x')(0o777) == 0o766
+- True
+-
+- >>> _permission_mask('o-x')(0o445) == 0o444
+- True
+-
+- >>> _permission_mask('a+x')(0) == 0o111
+- True
+-
+- >>> _permission_mask('a=rw')(0o057) == 0o666
+- True
+-
+- >>> _permission_mask('u=x')(0o666) == 0o166
+- True
+-
+- >>> _permission_mask('g=')(0o157) == 0o107
+- True
+- """
+- # parse the symbolic mode
+- parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
+- if not parsed:
+- raise ValueError("Unrecognized symbolic mode", mode)
+-
+- # generate a mask representing the specified permission
+- spec_map = dict(r=4, w=2, x=1)
+- specs = (spec_map[perm] for perm in parsed.group('what'))
+- spec = functools.reduce(operator.or_, specs, 0)
+-
+- # now apply spec to each subject in who
+- shift_map = dict(u=6, g=3, o=0)
+- who = parsed.group('who').replace('a', 'ugo')
+- masks = (spec << shift_map[subj] for subj in who)
+- mask = functools.reduce(operator.or_, masks)
+-
+- op = parsed.group('op')
+-
+- # if op is -, invert the mask
+- if op == '-':
+- mask ^= 0o777
+-
+- # if op is =, retain extant values for unreferenced subjects
+- if op == '=':
+- masks = (0o7 << shift_map[subj] for subj in who)
+- retain = functools.reduce(operator.or_, masks) ^ 0o777
+-
+- op_map = {
+- '+': operator.or_,
+- '-': operator.and_,
+- '=': lambda mask, target: target & retain ^ mask,
+- }
+- return functools.partial(op_map[op], mask)
+-
+-
+-class CaseInsensitivePattern(text_type):
+- """
+- A string with a ``'normcase'`` property, suitable for passing to
+- :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
+- :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
+-
+- For example, to get all files ending in .py, .Py, .pY, or .PY in the
+- current directory::
+-
+- from path import Path, CaseInsensitivePattern as ci
+- Path('.').files(ci('*.py'))
+- """
+-
+- @property
+- def normcase(self):
+- return __import__('ntpath').normcase
+-
+-########################
+-# Backward-compatibility
+-class path(Path):
+- def __new__(cls, *args, **kwargs):
+- msg = "path is deprecated. Use Path instead."
+- warnings.warn(msg, DeprecationWarning)
+- return Path.__new__(cls, *args, **kwargs)
+-
+-
+-__all__ += ['path']
+-########################
+diff --git a/tasks/_vendor/pathlib.py b/tasks/_vendor/pathlib.py
+deleted file mode 100644
+index 9ab0e70..0000000
+--- a/tasks/_vendor/pathlib.py
++++ /dev/null
+@@ -1,1280 +0,0 @@
+-import fnmatch
+-import functools
+-import io
+-import ntpath
+-import os
+-import posixpath
+-import re
+-import sys
+-import time
+-from collections import Sequence
+-from contextlib import contextmanager
+-from errno import EINVAL, ENOENT
+-from operator import attrgetter
+-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+-try:
+- from urllib import quote as urlquote, quote as urlquote_from_bytes
+-except ImportError:
+- from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+-
+-
+-try:
+- intern = intern
+-except NameError:
+- intern = sys.intern
+-try:
+- basestring = basestring
+-except NameError:
+- basestring = str
+-
+-supports_symlinks = True
+-try:
+- import nt
+-except ImportError:
+- nt = None
+-else:
+- if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+- from nt import _getfinalpathname
+- else:
+- supports_symlinks = False
+- _getfinalpathname = None
+-
+-
+-__all__ = [
+- "PurePath", "PurePosixPath", "PureWindowsPath",
+- "Path", "PosixPath", "WindowsPath",
+- ]
+-
+-#
+-# Internals
+-#
+-
+-_py2 = sys.version_info < (3,)
+-_py2_fs_encoding = 'ascii'
+-
+-def _py2_fsencode(parts):
+- # py2 => minimal unicode support
+- return [part.encode(_py2_fs_encoding) if isinstance(part, unicode)
+- else part for part in parts]
+-
+-def _is_wildcard_pattern(pat):
+- # Whether this pattern needs actual matching using fnmatch, or can
+- # be looked up directly as a file.
+- return "*" in pat or "?" in pat or "[" in pat
+-
+-
+-class _Flavour(object):
+- """A flavour implements a particular (platform-specific) set of path
+- semantics."""
+-
+- def __init__(self):
+- self.join = self.sep.join
+-
+- def parse_parts(self, parts):
+- if _py2:
+- parts = _py2_fsencode(parts)
+- parsed = []
+- sep = self.sep
+- altsep = self.altsep
+- drv = root = ''
+- it = reversed(parts)
+- for part in it:
+- if not part:
+- continue
+- if altsep:
+- part = part.replace(altsep, sep)
+- drv, root, rel = self.splitroot(part)
+- if sep in rel:
+- for x in reversed(rel.split(sep)):
+- if x and x != '.':
+- parsed.append(intern(x))
+- else:
+- if rel and rel != '.':
+- parsed.append(intern(rel))
+- if drv or root:
+- if not drv:
+- # If no drive is present, try to find one in the previous
+- # parts. This makes the result of parsing e.g.
+- # ("C:", "/", "a") reasonably intuitive.
+- for part in it:
+- drv = self.splitroot(part)[0]
+- if drv:
+- break
+- break
+- if drv or root:
+- parsed.append(drv + root)
+- parsed.reverse()
+- return drv, root, parsed
+-
+- def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+- """
+- Join the two paths represented by the respective
+- (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+- """
+- if root2:
+- if not drv2 and drv:
+- return drv, root2, [drv + root2] + parts2[1:]
+- elif drv2:
+- if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+- # Same drive => second path is relative to the first
+- return drv, root, parts + parts2[1:]
+- else:
+- # Second path is non-anchored (common case)
+- return drv, root, parts + parts2
+- return drv2, root2, parts2
+-
+-
+-class _WindowsFlavour(_Flavour):
+- # Reference for Windows paths can be found at
+- # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+-
+- sep = '\\'
+- altsep = '/'
+- has_drv = True
+- pathmod = ntpath
+-
+- is_supported = (nt is not None)
+-
+- drive_letters = (
+- set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+- set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+- )
+- ext_namespace_prefix = '\\\\?\\'
+-
+- reserved_names = (
+- set(['CON', 'PRN', 'AUX', 'NUL']) |
+- set(['COM%d' % i for i in range(1, 10)]) |
+- set(['LPT%d' % i for i in range(1, 10)])
+- )
+-
+- # Interesting findings about extended paths:
+- # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+- # but '\\?\c:/a' is not
+- # - extended paths are always absolute; "relative" extended paths will
+- # fail.
+-
+- def splitroot(self, part, sep=sep):
+- first = part[0:1]
+- second = part[1:2]
+- if (second == sep and first == sep):
+- # XXX extended paths should also disable the collapsing of "."
+- # components (according to MSDN docs).
+- prefix, part = self._split_extended_path(part)
+- first = part[0:1]
+- second = part[1:2]
+- else:
+- prefix = ''
+- third = part[2:3]
+- if (second == sep and first == sep and third != sep):
+- # is a UNC path:
+- # vvvvvvvvvvvvvvvvvvvvv root
+- # \\machine\mountpoint\directory\etc\...
+- # directory ^^^^^^^^^^^^^^
+- index = part.find(sep, 2)
+- if index != -1:
+- index2 = part.find(sep, index + 1)
+- # a UNC path can't have two slashes in a row
+- # (after the initial two)
+- if index2 != index + 1:
+- if index2 == -1:
+- index2 = len(part)
+- if prefix:
+- return prefix + part[1:index2], sep, part[index2+1:]
+- else:
+- return part[:index2], sep, part[index2+1:]
+- drv = root = ''
+- if second == ':' and first in self.drive_letters:
+- drv = part[:2]
+- part = part[2:]
+- first = third
+- if first == sep:
+- root = first
+- part = part.lstrip(sep)
+- return prefix + drv, root, part
+-
+- def casefold(self, s):
+- return s.lower()
+-
+- def casefold_parts(self, parts):
+- return [p.lower() for p in parts]
+-
+- def resolve(self, path):
+- s = str(path)
+- if not s:
+- return os.getcwd()
+- if _getfinalpathname is not None:
+- return self._ext_to_normal(_getfinalpathname(s))
+- # Means fallback on absolute
+- return None
+-
+- def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+- prefix = ''
+- if s.startswith(ext_prefix):
+- prefix = s[:4]
+- s = s[4:]
+- if s.startswith('UNC\\'):
+- prefix += s[:3]
+- s = '\\' + s[3:]
+- return prefix, s
+-
+- def _ext_to_normal(self, s):
+- # Turn back an extended path into a normal DOS-like path
+- return self._split_extended_path(s)[1]
+-
+- def is_reserved(self, parts):
+- # NOTE: the rules for reserved names seem somewhat complicated
+- # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+- # We err on the side of caution and return True for paths which are
+- # not considered reserved by Windows.
+- if not parts:
+- return False
+- if parts[0].startswith('\\\\'):
+- # UNC paths are never reserved
+- return False
+- return parts[-1].partition('.')[0].upper() in self.reserved_names
+-
+- def make_uri(self, path):
+- # Under Windows, file URIs use the UTF-8 encoding.
+- drive = path.drive
+- if len(drive) == 2 and drive[1] == ':':
+- # It's a path on a local drive => 'file:///c:/a/b'
+- rest = path.as_posix()[2:].lstrip('/')
+- return 'file:///%s/%s' % (
+- drive, urlquote_from_bytes(rest.encode('utf-8')))
+- else:
+- # It's a path on a network drive => 'file://host/share/a/b'
+- return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+-
+-
+-class _PosixFlavour(_Flavour):
+- sep = '/'
+- altsep = ''
+- has_drv = False
+- pathmod = posixpath
+-
+- is_supported = (os.name != 'nt')
+-
+- def splitroot(self, part, sep=sep):
+- if part and part[0] == sep:
+- stripped_part = part.lstrip(sep)
+- # According to POSIX path resolution:
+- # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+- # "A pathname that begins with two successive slashes may be
+- # interpreted in an implementation-defined manner, although more
+- # than two leading slashes shall be treated as a single slash".
+- if len(part) - len(stripped_part) == 2:
+- return '', sep * 2, stripped_part
+- else:
+- return '', sep, stripped_part
+- else:
+- return '', '', part
+-
+- def casefold(self, s):
+- return s
+-
+- def casefold_parts(self, parts):
+- return parts
+-
+- def resolve(self, path):
+- sep = self.sep
+- accessor = path._accessor
+- seen = {}
+- def _resolve(path, rest):
+- if rest.startswith(sep):
+- path = ''
+-
+- for name in rest.split(sep):
+- if not name or name == '.':
+- # current dir
+- continue
+- if name == '..':
+- # parent dir
+- path, _, _ = path.rpartition(sep)
+- continue
+- newpath = path + sep + name
+- if newpath in seen:
+- # Already seen this path
+- path = seen[newpath]
+- if path is not None:
+- # use cached value
+- continue
+- # The symlink is not resolved, so we must have a symlink loop.
+- raise RuntimeError("Symlink loop from %r" % newpath)
+- # Resolve the symbolic link
+- try:
+- target = accessor.readlink(newpath)
+- except OSError as e:
+- if e.errno != EINVAL:
+- raise
+- # Not a symlink
+- path = newpath
+- else:
+- seen[newpath] = None # not resolved symlink
+- path = _resolve(path, target)
+- seen[newpath] = path # resolved symlink
+-
+- return path
+- # NOTE: according to POSIX, getcwd() cannot contain path components
+- # which are symlinks.
+- base = '' if path.is_absolute() else os.getcwd()
+- return _resolve(base, str(path)) or sep
+-
+- def is_reserved(self, parts):
+- return False
+-
+- def make_uri(self, path):
+- # We represent the path using the local filesystem encoding,
+- # for portability to other applications.
+- bpath = bytes(path)
+- return 'file://' + urlquote_from_bytes(bpath)
+-
+-
+-_windows_flavour = _WindowsFlavour()
+-_posix_flavour = _PosixFlavour()
+-
+-
+-class _Accessor:
+- """An accessor implements a particular (system-specific or not) way of
+- accessing paths on the filesystem."""
+-
+-
+-class _NormalAccessor(_Accessor):
+-
+- def _wrap_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobj, *args):
+- return strfunc(str(pathobj), *args)
+- return staticmethod(wrapped)
+-
+- def _wrap_binary_strfunc(strfunc):
+- @functools.wraps(strfunc)
+- def wrapped(pathobjA, pathobjB, *args):
+- return strfunc(str(pathobjA), str(pathobjB), *args)
+- return staticmethod(wrapped)
+-
+- stat = _wrap_strfunc(os.stat)
+-
+- lstat = _wrap_strfunc(os.lstat)
+-
+- open = _wrap_strfunc(os.open)
+-
+- listdir = _wrap_strfunc(os.listdir)
+-
+- chmod = _wrap_strfunc(os.chmod)
+-
+- if hasattr(os, "lchmod"):
+- lchmod = _wrap_strfunc(os.lchmod)
+- else:
+- def lchmod(self, pathobj, mode):
+- raise NotImplementedError("lchmod() not available on this system")
+-
+- mkdir = _wrap_strfunc(os.mkdir)
+-
+- unlink = _wrap_strfunc(os.unlink)
+-
+- rmdir = _wrap_strfunc(os.rmdir)
+-
+- rename = _wrap_binary_strfunc(os.rename)
+-
+- if sys.version_info >= (3, 3):
+- replace = _wrap_binary_strfunc(os.replace)
+-
+- if nt:
+- if supports_symlinks:
+- symlink = _wrap_binary_strfunc(os.symlink)
+- else:
+- def symlink(a, b, target_is_directory):
+- raise NotImplementedError("symlink() not available on this system")
+- else:
+- # Under POSIX, os.symlink() takes two args
+- @staticmethod
+- def symlink(a, b, target_is_directory):
+- return os.symlink(str(a), str(b))
+-
+- utime = _wrap_strfunc(os.utime)
+-
+- # Helper for resolve()
+- def readlink(self, path):
+- return os.readlink(path)
+-
+-
+-_normal_accessor = _NormalAccessor()
+-
+-
+-#
+-# Globbing helpers
+-#
+-
+-@contextmanager
+-def _cached(func):
+- try:
+- func.__cached__
+- yield func
+- except AttributeError:
+- cache = {}
+- def wrapper(*args):
+- try:
+- return cache[args]
+- except KeyError:
+- value = cache[args] = func(*args)
+- return value
+- wrapper.__cached__ = True
+- try:
+- yield wrapper
+- finally:
+- cache.clear()
+-
+-def _make_selector(pattern_parts):
+- pat = pattern_parts[0]
+- child_parts = pattern_parts[1:]
+- if pat == '**':
+- cls = _RecursiveWildcardSelector
+- elif '**' in pat:
+- raise ValueError("Invalid pattern: '**' can only be an entire path component")
+- elif _is_wildcard_pattern(pat):
+- cls = _WildcardSelector
+- else:
+- cls = _PreciseSelector
+- return cls(pat, child_parts)
+-
+-if hasattr(functools, "lru_cache"):
+- _make_selector = functools.lru_cache()(_make_selector)
+-
+-
+-class _Selector:
+- """A selector matches a specific glob pattern part against the children
+- of a given path."""
+-
+- def __init__(self, child_parts):
+- self.child_parts = child_parts
+- if child_parts:
+- self.successor = _make_selector(child_parts)
+- else:
+- self.successor = _TerminatingSelector()
+-
+- def select_from(self, parent_path):
+- """Iterate over all child paths of `parent_path` matched by this
+- selector. This can contain parent_path itself."""
+- path_cls = type(parent_path)
+- is_dir = path_cls.is_dir
+- exists = path_cls.exists
+- listdir = parent_path._accessor.listdir
+- return self._select_from(parent_path, is_dir, exists, listdir)
+-
+-
+-class _TerminatingSelector:
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- yield parent_path
+-
+-
+-class _PreciseSelector(_Selector):
+-
+- def __init__(self, name, child_parts):
+- self.name = name
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- path = parent_path._make_child_relpath(self.name)
+- if exists(path):
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _WildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- self.pat = re.compile(fnmatch.translate(pat))
+- _Selector.__init__(self, child_parts)
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- cf = parent_path._flavour.casefold
+- for name in listdir(parent_path):
+- casefolded = cf(name)
+- if self.pat.match(casefolded):
+- path = parent_path._make_child_relpath(name)
+- for p in self.successor._select_from(path, is_dir, exists, listdir):
+- yield p
+-
+-
+-class _RecursiveWildcardSelector(_Selector):
+-
+- def __init__(self, pat, child_parts):
+- _Selector.__init__(self, child_parts)
+-
+- def _iterate_directories(self, parent_path, is_dir, listdir):
+- yield parent_path
+- for name in listdir(parent_path):
+- path = parent_path._make_child_relpath(name)
+- if is_dir(path):
+- for p in self._iterate_directories(path, is_dir, listdir):
+- yield p
+-
+- def _select_from(self, parent_path, is_dir, exists, listdir):
+- if not is_dir(parent_path):
+- return
+- with _cached(listdir) as listdir:
+- yielded = set()
+- try:
+- successor_select = self.successor._select_from
+- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+- for p in successor_select(starting_point, is_dir, exists, listdir):
+- if p not in yielded:
+- yield p
+- yielded.add(p)
+- finally:
+- yielded.clear()
+-
+-
+-#
+-# Public API
+-#
+-
+-class _PathParents(Sequence):
+- """This object provides sequence-like access to the logical ancestors
+- of a path. Don't try to construct it yourself."""
+- __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+-
+- def __init__(self, path):
+- # We don't store the instance to avoid reference cycles
+- self._pathcls = type(path)
+- self._drv = path._drv
+- self._root = path._root
+- self._parts = path._parts
+-
+- def __len__(self):
+- if self._drv or self._root:
+- return len(self._parts) - 1
+- else:
+- return len(self._parts)
+-
+- def __getitem__(self, idx):
+- if idx < 0 or idx >= len(self):
+- raise IndexError(idx)
+- return self._pathcls._from_parsed_parts(self._drv, self._root,
+- self._parts[:-idx - 1])
+-
+- def __repr__(self):
+- return "<{0}.parents>".format(self._pathcls.__name__)
+-
+-
+-class PurePath(object):
+- """PurePath represents a filesystem path and offers operations which
+- don't imply any actual filesystem I/O. Depending on your system,
+- instantiating a PurePath will return either a PurePosixPath or a
+- PureWindowsPath object. You can also instantiate either of these classes
+- directly, regardless of your system.
+- """
+- __slots__ = (
+- '_drv', '_root', '_parts',
+- '_str', '_hash', '_pparts', '_cached_cparts',
+- )
+-
+- def __new__(cls, *args):
+- """Construct a PurePath from one or several strings and or existing
+- PurePath objects. The strings and path objects are combined so as
+- to yield a canonicalized path, which is incorporated into the
+- new PurePath object.
+- """
+- if cls is PurePath:
+- cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+- return cls._from_parts(args)
+-
+- def __reduce__(self):
+- # Using the parts tuple helps share interned path parts
+- # when pickling related paths.
+- return (self.__class__, tuple(self._parts))
+-
+- @classmethod
+- def _parse_args(cls, args):
+- # This is useful when you don't want to create an instance, just
+- # canonicalize some constructor arguments.
+- parts = []
+- for a in args:
+- if isinstance(a, PurePath):
+- parts += a._parts
+- elif isinstance(a, basestring):
+- parts.append(a)
+- else:
+- raise TypeError(
+- "argument should be a path or str object, not %r"
+- % type(a))
+- return cls._flavour.parse_parts(parts)
+-
+- @classmethod
+- def _from_parts(cls, args, init=True):
+- # We need to call _parse_args on the instance, so as to get the
+- # right flavour.
+- self = object.__new__(cls)
+- drv, root, parts = self._parse_args(args)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _from_parsed_parts(cls, drv, root, parts, init=True):
+- self = object.__new__(cls)
+- self._drv = drv
+- self._root = root
+- self._parts = parts
+- if init:
+- self._init()
+- return self
+-
+- @classmethod
+- def _format_parsed_parts(cls, drv, root, parts):
+- if drv or root:
+- return drv + root + cls._flavour.join(parts[1:])
+- else:
+- return cls._flavour.join(parts)
+-
+- def _init(self):
+- # Overriden in concrete Path
+- pass
+-
+- def _make_child(self, args):
+- drv, root, parts = self._parse_args(args)
+- drv, root, parts = self._flavour.join_parsed_parts(
+- self._drv, self._root, self._parts, drv, root, parts)
+- return self._from_parsed_parts(drv, root, parts)
+-
+- def __str__(self):
+- """Return the string representation of the path, suitable for
+- passing to system calls."""
+- try:
+- return self._str
+- except AttributeError:
+- self._str = self._format_parsed_parts(self._drv, self._root,
+- self._parts) or '.'
+- return self._str
+-
+- def as_posix(self):
+- """Return the string representation of the path with forward (/)
+- slashes."""
+- f = self._flavour
+- return str(self).replace(f.sep, '/')
+-
+- def __bytes__(self):
+- """Return the bytes representation of the path. This is only
+- recommended to use under Unix."""
+- if sys.version_info < (3, 2):
+- raise NotImplementedError("needs Python 3.2 or later")
+- return os.fsencode(str(self))
+-
+- def __repr__(self):
+- return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+-
+- def as_uri(self):
+- """Return the path as a 'file' URI."""
+- if not self.is_absolute():
+- raise ValueError("relative path can't be expressed as a file URI")
+- return self._flavour.make_uri(self)
+-
+- @property
+- def _cparts(self):
+- # Cached casefolded parts, for hashing and comparison
+- try:
+- return self._cached_cparts
+- except AttributeError:
+- self._cached_cparts = self._flavour.casefold_parts(self._parts)
+- return self._cached_cparts
+-
+- def __eq__(self, other):
+- if not isinstance(other, PurePath):
+- return NotImplemented
+- return self._cparts == other._cparts and self._flavour is other._flavour
+-
+- def __ne__(self, other):
+- return not self == other
+-
+- def __hash__(self):
+- try:
+- return self._hash
+- except AttributeError:
+- self._hash = hash(tuple(self._cparts))
+- return self._hash
+-
+- def __lt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts < other._cparts
+-
+- def __le__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts <= other._cparts
+-
+- def __gt__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts > other._cparts
+-
+- def __ge__(self, other):
+- if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+- return NotImplemented
+- return self._cparts >= other._cparts
+-
+- drive = property(attrgetter('_drv'),
+- doc="""The drive prefix (letter or UNC path), if any.""")
+-
+- root = property(attrgetter('_root'),
+- doc="""The root of the path, if any.""")
+-
+- @property
+- def anchor(self):
+- """The concatenation of the drive and root, or ''."""
+- anchor = self._drv + self._root
+- return anchor
+-
+- @property
+- def name(self):
+- """The final path component, if any."""
+- parts = self._parts
+- if len(parts) == (1 if (self._drv or self._root) else 0):
+- return ''
+- return parts[-1]
+-
+- @property
+- def suffix(self):
+- """The final component's last suffix, if any."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[i:]
+- else:
+- return ''
+-
+- @property
+- def suffixes(self):
+- """A list of the final component's suffixes, if any."""
+- name = self.name
+- if name.endswith('.'):
+- return []
+- name = name.lstrip('.')
+- return ['.' + suffix for suffix in name.split('.')[1:]]
+-
+- @property
+- def stem(self):
+- """The final path component, minus its last suffix."""
+- name = self.name
+- i = name.rfind('.')
+- if 0 < i < len(name) - 1:
+- return name[:i]
+- else:
+- return name
+-
+- def with_name(self, name):
+- """Return a new path with the file name changed."""
+- if not self.name:
+- raise ValueError("%r has an empty name" % (self,))
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def with_suffix(self, suffix):
+- """Return a new path with the file suffix changed (or added, if none)."""
+- # XXX if suffix is None, should the current suffix be removed?
+- drv, root, parts = self._flavour.parse_parts((suffix,))
+- if drv or root or len(parts) != 1:
+- raise ValueError("Invalid suffix %r" % (suffix))
+- suffix = parts[0]
+- if not suffix.startswith('.'):
+- raise ValueError("Invalid suffix %r" % (suffix))
+- name = self.name
+- if not name:
+- raise ValueError("%r has an empty name" % (self,))
+- old_suffix = self.suffix
+- if not old_suffix:
+- name = name + suffix
+- else:
+- name = name[:-len(old_suffix)] + suffix
+- return self._from_parsed_parts(self._drv, self._root,
+- self._parts[:-1] + [name])
+-
+- def relative_to(self, *other):
+- """Return the relative path to another path identified by the passed
+- arguments. If the operation is not possible (because this is not
+- a subpath of the other path), raise ValueError.
+- """
+- # For the purpose of this method, drive and root are considered
+- # separate parts, i.e.:
+- # Path('c:/').relative_to('c:') gives Path('/')
+- # Path('c:/').relative_to('/') raise ValueError
+- if not other:
+- raise TypeError("need at least one argument")
+- parts = self._parts
+- drv = self._drv
+- root = self._root
+- if root:
+- abs_parts = [drv, root] + parts[1:]
+- else:
+- abs_parts = parts
+- to_drv, to_root, to_parts = self._parse_args(other)
+- if to_root:
+- to_abs_parts = [to_drv, to_root] + to_parts[1:]
+- else:
+- to_abs_parts = to_parts
+- n = len(to_abs_parts)
+- cf = self._flavour.casefold_parts
+- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+- formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+- raise ValueError("{!r} does not start with {!r}"
+- .format(str(self), str(formatted)))
+- return self._from_parsed_parts('', root if n == 1 else '',
+- abs_parts[n:])
+-
+- @property
+- def parts(self):
+- """An object providing sequence-like access to the
+- components in the filesystem path."""
+- # We cache the tuple to avoid building a new one each time .parts
+- # is accessed. XXX is this necessary?
+- try:
+- return self._pparts
+- except AttributeError:
+- self._pparts = tuple(self._parts)
+- return self._pparts
+-
+- def joinpath(self, *args):
+- """Combine this path with one or several arguments, and return a
+- new path representing either a subpath (if all arguments are relative
+- paths) or a totally different path (if one of the arguments is
+- anchored).
+- """
+- return self._make_child(args)
+-
+- def __truediv__(self, key):
+- return self._make_child((key,))
+-
+- def __rtruediv__(self, key):
+- return self._from_parts([key] + self._parts)
+-
+- if sys.version_info < (3,):
+- __div__ = __truediv__
+- __rdiv__ = __rtruediv__
+-
+- @property
+- def parent(self):
+- """The logical parent of the path."""
+- drv = self._drv
+- root = self._root
+- parts = self._parts
+- if len(parts) == 1 and (drv or root):
+- return self
+- return self._from_parsed_parts(drv, root, parts[:-1])
+-
+- @property
+- def parents(self):
+- """A sequence of this path's logical parents."""
+- return _PathParents(self)
+-
+- def is_absolute(self):
+- """True if the path is absolute (has both a root and, if applicable,
+- a drive)."""
+- if not self._root:
+- return False
+- return not self._flavour.has_drv or bool(self._drv)
+-
+- def is_reserved(self):
+- """Return True if the path contains one of the special names reserved
+- by the system, if any."""
+- return self._flavour.is_reserved(self._parts)
+-
+- def match(self, path_pattern):
+- """
+- Return True if this path matches the given pattern.
+- """
+- cf = self._flavour.casefold
+- path_pattern = cf(path_pattern)
+- drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+- if not pat_parts:
+- raise ValueError("empty pattern")
+- if drv and drv != cf(self._drv):
+- return False
+- if root and root != cf(self._root):
+- return False
+- parts = self._cparts
+- if drv or root:
+- if len(pat_parts) != len(parts):
+- return False
+- pat_parts = pat_parts[1:]
+- elif len(pat_parts) > len(parts):
+- return False
+- for part, pat in zip(reversed(parts), reversed(pat_parts)):
+- if not fnmatch.fnmatchcase(part, pat):
+- return False
+- return True
+-
+-
+-class PurePosixPath(PurePath):
+- _flavour = _posix_flavour
+- __slots__ = ()
+-
+-
+-class PureWindowsPath(PurePath):
+- _flavour = _windows_flavour
+- __slots__ = ()
+-
+-
+-# Filesystem-accessing classes
+-
+-
+-class Path(PurePath):
+- __slots__ = (
+- '_accessor',
+- )
+-
+- def __new__(cls, *args, **kwargs):
+- if cls is Path:
+- cls = WindowsPath if os.name == 'nt' else PosixPath
+- self = cls._from_parts(args, init=False)
+- if not self._flavour.is_supported:
+- raise NotImplementedError("cannot instantiate %r on your system"
+- % (cls.__name__,))
+- self._init()
+- return self
+-
+- def _init(self,
+- # Private non-constructor arguments
+- template=None,
+- ):
+- if template is not None:
+- self._accessor = template._accessor
+- else:
+- self._accessor = _normal_accessor
+-
+- def _make_child_relpath(self, part):
+- # This is an optimization used for dir walking. `part` must be
+- # a single part relative to this path.
+- parts = self._parts + [part]
+- return self._from_parsed_parts(self._drv, self._root, parts)
+-
+- def _opener(self, name, flags, mode=0o666):
+- # A stub for the opener argument to built-in open()
+- return self._accessor.open(self, flags, mode)
+-
+- def _raw_open(self, flags, mode=0o777):
+- """
+- Open the file pointed by this path and return a file descriptor,
+- as os.open() does.
+- """
+- return self._accessor.open(self, flags, mode)
+-
+- # Public API
+-
+- @classmethod
+- def cwd(cls):
+- """Return a new path pointing to the current working directory
+- (as returned by os.getcwd()).
+- """
+- return cls(os.getcwd())
+-
+- def iterdir(self):
+- """Iterate over the files in this directory. Does not yield any
+- result for the special paths '.' and '..'.
+- """
+- for name in self._accessor.listdir(self):
+- if name in ('.', '..'):
+- # Yielding a path object for these makes little sense
+- continue
+- yield self._make_child_relpath(name)
+-
+- def glob(self, pattern):
+- """Iterate over this subtree and yield all existing files (of any
+- kind, including directories) matching the given pattern.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def rglob(self, pattern):
+- """Recursively yield all existing files (of any kind, including
+- directories) matching the given pattern, anywhere in this subtree.
+- """
+- pattern = self._flavour.casefold(pattern)
+- drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+- if drv or root:
+- raise NotImplementedError("Non-relative patterns are unsupported")
+- selector = _make_selector(("**",) + tuple(pattern_parts))
+- for p in selector.select_from(self):
+- yield p
+-
+- def absolute(self):
+- """Return an absolute version of this path. This function works
+- even if the path doesn't point to anything.
+-
+- No normalization is done, i.e. all '.' and '..' will be kept along.
+- Use resolve() to get the canonical path to a file.
+- """
+- # XXX untested yet!
+- if self.is_absolute():
+- return self
+- # FIXME this must defer to the specific flavour (and, under Windows,
+- # use nt._getfullpathname())
+- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+- obj._init(template=self)
+- return obj
+-
+- def resolve(self):
+- """
+- Make the path absolute, resolving all symlinks on the way and also
+- normalizing it (for example turning slashes into backslashes under
+- Windows).
+- """
+- s = self._flavour.resolve(self)
+- if s is None:
+- # No symlink resolution => for consistency, raise an error if
+- # the path doesn't exist or is forbidden
+- self.stat()
+- s = str(self.absolute())
+- # Now we have no symlinks in the path, it's safe to normalize it.
+- normed = self._flavour.pathmod.normpath(s)
+- obj = self._from_parts((normed,), init=False)
+- obj._init(template=self)
+- return obj
+-
+- def stat(self):
+- """
+- Return the result of the stat() system call on this path, like
+- os.stat() does.
+- """
+- return self._accessor.stat(self)
+-
+- def owner(self):
+- """
+- Return the login name of the file owner.
+- """
+- import pwd
+- return pwd.getpwuid(self.stat().st_uid).pw_name
+-
+- def group(self):
+- """
+- Return the group name of the file gid.
+- """
+- import grp
+- return grp.getgrgid(self.stat().st_gid).gr_name
+-
+- def open(self, mode='r', buffering=-1, encoding=None,
+- errors=None, newline=None):
+- """
+- Open the file pointed by this path and return a file object, as
+- the built-in open() function does.
+- """
+- if sys.version_info >= (3, 3):
+- return io.open(str(self), mode, buffering, encoding, errors, newline,
+- opener=self._opener)
+- else:
+- return io.open(str(self), mode, buffering, encoding, errors, newline)
+-
+- def touch(self, mode=0o666, exist_ok=True):
+- """
+- Create this file with the given access mode, if it doesn't exist.
+- """
+- if exist_ok:
+- # First try to bump modification time
+- # Implementation note: GNU touch uses the UTIME_NOW option of
+- # the utimensat() / futimens() functions.
+- t = time.time()
+- try:
+- self._accessor.utime(self, (t, t))
+- except OSError:
+- # Avoid exception chaining
+- pass
+- else:
+- return
+- flags = os.O_CREAT | os.O_WRONLY
+- if not exist_ok:
+- flags |= os.O_EXCL
+- fd = self._raw_open(flags, mode)
+- os.close(fd)
+-
+- def mkdir(self, mode=0o777, parents=False):
+- if not parents:
+- self._accessor.mkdir(self, mode)
+- else:
+- try:
+- self._accessor.mkdir(self, mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- self.parent.mkdir(parents=True)
+- self._accessor.mkdir(self, mode)
+-
+- def chmod(self, mode):
+- """
+- Change the permissions of the path, like os.chmod().
+- """
+- self._accessor.chmod(self, mode)
+-
+- def lchmod(self, mode):
+- """
+- Like chmod(), except if the path points to a symlink, the symlink's
+- permissions are changed, rather than its target's.
+- """
+- self._accessor.lchmod(self, mode)
+-
+- def unlink(self):
+- """
+- Remove this file or link.
+- If the path is a directory, use rmdir() instead.
+- """
+- self._accessor.unlink(self)
+-
+- def rmdir(self):
+- """
+- Remove this directory. The directory must be empty.
+- """
+- self._accessor.rmdir(self)
+-
+- def lstat(self):
+- """
+- Like stat(), except if the path points to a symlink, the symlink's
+- status information is returned, rather than its target's.
+- """
+- return self._accessor.lstat(self)
+-
+- def rename(self, target):
+- """
+- Rename this path to the given path.
+- """
+- self._accessor.rename(self, target)
+-
+- def replace(self, target):
+- """
+- Rename this path to the given path, clobbering the existing
+- destination if it exists.
+- """
+- if sys.version_info < (3, 3):
+- raise NotImplementedError("replace() is only available "
+- "with Python 3.3 and later")
+- self._accessor.replace(self, target)
+-
+- def symlink_to(self, target, target_is_directory=False):
+- """
+- Make this path a symlink pointing to the given path.
+- Note the order of arguments (self, target) is the reverse of os.symlink's.
+- """
+- self._accessor.symlink(target, self, target_is_directory)
+-
+- # Convenience functions for querying the stat results
+-
+- def exists(self):
+- """
+- Whether this path exists.
+- """
+- try:
+- self.stat()
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- return False
+- return True
+-
+- def is_dir(self):
+- """
+- Whether this path is a directory.
+- """
+- try:
+- return S_ISDIR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_file(self):
+- """
+- Whether this path is a regular file (also True for symlinks pointing
+- to regular files).
+- """
+- try:
+- return S_ISREG(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_symlink(self):
+- """
+- Whether this path is a symbolic link.
+- """
+- try:
+- return S_ISLNK(self.lstat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist
+- return False
+-
+- def is_block_device(self):
+- """
+- Whether this path is a block device.
+- """
+- try:
+- return S_ISBLK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_char_device(self):
+- """
+- Whether this path is a character device.
+- """
+- try:
+- return S_ISCHR(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_fifo(self):
+- """
+- Whether this path is a FIFO.
+- """
+- try:
+- return S_ISFIFO(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+- def is_socket(self):
+- """
+- Whether this path is a socket.
+- """
+- try:
+- return S_ISSOCK(self.stat().st_mode)
+- except OSError as e:
+- if e.errno != ENOENT:
+- raise
+- # Path doesn't exist or is a broken symlink
+- # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+- return False
+-
+-
+-class PosixPath(Path, PurePosixPath):
+- __slots__ = ()
+-
+-class WindowsPath(Path, PureWindowsPath):
+- __slots__ = ()
+-
+diff --git a/tasks/_vendor/six.py b/tasks/_vendor/six.py
+deleted file mode 100644
+index 190c023..0000000
+--- a/tasks/_vendor/six.py
++++ /dev/null
+@@ -1,868 +0,0 @@
+-"""Utilities for writing code that runs on Python 2 and 3"""
+-
+-# Copyright (c) 2010-2015 Benjamin Peterson
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-from __future__ import absolute_import
+-
+-import functools
+-import itertools
+-import operator
+-import sys
+-import types
+-
+-__author__ = "Benjamin Peterson <benjamin@python.org>"
+-__version__ = "1.10.0"
+-
+-
+-# Useful for very coarse version differentiation.
+-PY2 = sys.version_info[0] == 2
+-PY3 = sys.version_info[0] == 3
+-PY34 = sys.version_info[0:2] >= (3, 4)
+-
+-if PY3:
+- string_types = str,
+- integer_types = int,
+- class_types = type,
+- text_type = str
+- binary_type = bytes
+-
+- MAXSIZE = sys.maxsize
+-else:
+- string_types = basestring,
+- integer_types = (int, long)
+- class_types = (type, types.ClassType)
+- text_type = unicode
+- binary_type = str
+-
+- if sys.platform.startswith("java"):
+- # Jython always uses 32 bits.
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+- class X(object):
+-
+- def __len__(self):
+- return 1 << 31
+- try:
+- len(X())
+- except OverflowError:
+- # 32-bit
+- MAXSIZE = int((1 << 31) - 1)
+- else:
+- # 64-bit
+- MAXSIZE = int((1 << 63) - 1)
+- del X
+-
+-
+-def _add_doc(func, doc):
+- """Add documentation to a function."""
+- func.__doc__ = doc
+-
+-
+-def _import_module(name):
+- """Import module, returning the module after the last dot."""
+- __import__(name)
+- return sys.modules[name]
+-
+-
+-class _LazyDescr(object):
+-
+- def __init__(self, name):
+- self.name = name
+-
+- def __get__(self, obj, tp):
+- result = self._resolve()
+- setattr(obj, self.name, result) # Invokes __set__.
+- try:
+- # This is a bit ugly, but it avoids running this again by
+- # removing this descriptor.
+- delattr(obj.__class__, self.name)
+- except AttributeError:
+- pass
+- return result
+-
+-
+-class MovedModule(_LazyDescr):
+-
+- def __init__(self, name, old, new=None):
+- super(MovedModule, self).__init__(name)
+- if PY3:
+- if new is None:
+- new = name
+- self.mod = new
+- else:
+- self.mod = old
+-
+- def _resolve(self):
+- return _import_module(self.mod)
+-
+- def __getattr__(self, attr):
+- _module = self._resolve()
+- value = getattr(_module, attr)
+- setattr(self, attr, value)
+- return value
+-
+-
+-class _LazyModule(types.ModuleType):
+-
+- def __init__(self, name):
+- super(_LazyModule, self).__init__(name)
+- self.__doc__ = self.__class__.__doc__
+-
+- def __dir__(self):
+- attrs = ["__doc__", "__name__"]
+- attrs += [attr.name for attr in self._moved_attributes]
+- return attrs
+-
+- # Subclasses should override this
+- _moved_attributes = []
+-
+-
+-class MovedAttribute(_LazyDescr):
+-
+- def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+- super(MovedAttribute, self).__init__(name)
+- if PY3:
+- if new_mod is None:
+- new_mod = name
+- self.mod = new_mod
+- if new_attr is None:
+- if old_attr is None:
+- new_attr = name
+- else:
+- new_attr = old_attr
+- self.attr = new_attr
+- else:
+- self.mod = old_mod
+- if old_attr is None:
+- old_attr = name
+- self.attr = old_attr
+-
+- def _resolve(self):
+- module = _import_module(self.mod)
+- return getattr(module, self.attr)
+-
+-
+-class _SixMetaPathImporter(object):
+-
+- """
+- A meta path importer to import six.moves and its submodules.
+-
+- This class implements a PEP302 finder and loader. It should be compatible
+- with Python 2.5 and all existing versions of Python3
+- """
+-
+- def __init__(self, six_module_name):
+- self.name = six_module_name
+- self.known_modules = {}
+-
+- def _add_module(self, mod, *fullnames):
+- for fullname in fullnames:
+- self.known_modules[self.name + "." + fullname] = mod
+-
+- def _get_module(self, fullname):
+- return self.known_modules[self.name + "." + fullname]
+-
+- def find_module(self, fullname, path=None):
+- if fullname in self.known_modules:
+- return self
+- return None
+-
+- def __get_module(self, fullname):
+- try:
+- return self.known_modules[fullname]
+- except KeyError:
+- raise ImportError("This loader does not know module " + fullname)
+-
+- def load_module(self, fullname):
+- try:
+- # in case of a reload
+- return sys.modules[fullname]
+- except KeyError:
+- pass
+- mod = self.__get_module(fullname)
+- if isinstance(mod, MovedModule):
+- mod = mod._resolve()
+- else:
+- mod.__loader__ = self
+- sys.modules[fullname] = mod
+- return mod
+-
+- def is_package(self, fullname):
+- """
+- Return true, if the named module is a package.
+-
+- We need this method to get correct spec objects with
+- Python 3.4 (see PEP451)
+- """
+- return hasattr(self.__get_module(fullname), "__path__")
+-
+- def get_code(self, fullname):
+- """Return None
+-
+- Required, if is_package is implemented"""
+- self.__get_module(fullname) # eventually raises ImportError
+- return None
+- get_source = get_code # same as get_code
+-
+-_importer = _SixMetaPathImporter(__name__)
+-
+-
+-class _MovedItems(_LazyModule):
+-
+- """Lazy loading of moved objects"""
+- __path__ = [] # mark as package
+-
+-
+-_moved_attributes = [
+- MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+- MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+- MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+- MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+- MovedAttribute("intern", "__builtin__", "sys"),
+- MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+- MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+- MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+- MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+- MovedAttribute("reduce", "__builtin__", "functools"),
+- MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+- MovedAttribute("StringIO", "StringIO", "io"),
+- MovedAttribute("UserDict", "UserDict", "collections"),
+- MovedAttribute("UserList", "UserList", "collections"),
+- MovedAttribute("UserString", "UserString", "collections"),
+- MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+- MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+- MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+- MovedModule("builtins", "__builtin__"),
+- MovedModule("configparser", "ConfigParser"),
+- MovedModule("copyreg", "copy_reg"),
+- MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+- MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+- MovedModule("http_cookies", "Cookie", "http.cookies"),
+- MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+- MovedModule("html_parser", "HTMLParser", "html.parser"),
+- MovedModule("http_client", "httplib", "http.client"),
+- MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+- MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+- MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+- MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+- MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+- MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+- MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+- MovedModule("cPickle", "cPickle", "pickle"),
+- MovedModule("queue", "Queue"),
+- MovedModule("reprlib", "repr"),
+- MovedModule("socketserver", "SocketServer"),
+- MovedModule("_thread", "thread", "_thread"),
+- MovedModule("tkinter", "Tkinter"),
+- MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+- MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+- MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+- MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+- MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+- MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+- MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+- MovedModule("tkinter_colorchooser", "tkColorChooser",
+- "tkinter.colorchooser"),
+- MovedModule("tkinter_commondialog", "tkCommonDialog",
+- "tkinter.commondialog"),
+- MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+- MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+- MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+- MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+- "tkinter.simpledialog"),
+- MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+- MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+- MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+- MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+- MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+- MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+-]
+-# Add windows specific modules.
+-if sys.platform == "win32":
+- _moved_attributes += [
+- MovedModule("winreg", "_winreg"),
+- ]
+-
+-for attr in _moved_attributes:
+- setattr(_MovedItems, attr.name, attr)
+- if isinstance(attr, MovedModule):
+- _importer._add_module(attr, "moves." + attr.name)
+-del attr
+-
+-_MovedItems._moved_attributes = _moved_attributes
+-
+-moves = _MovedItems(__name__ + ".moves")
+-_importer._add_module(moves, "moves")
+-
+-
+-class Module_six_moves_urllib_parse(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_parse"""
+-
+-
+-_urllib_parse_moved_attributes = [
+- MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+- MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+- MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+- MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+- MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+- MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+- MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+- MovedAttribute("quote", "urllib", "urllib.parse"),
+- MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("unquote", "urllib", "urllib.parse"),
+- MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+- MovedAttribute("urlencode", "urllib", "urllib.parse"),
+- MovedAttribute("splitquery", "urllib", "urllib.parse"),
+- MovedAttribute("splittag", "urllib", "urllib.parse"),
+- MovedAttribute("splituser", "urllib", "urllib.parse"),
+- MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+- MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+-]
+-for attr in _urllib_parse_moved_attributes:
+- setattr(Module_six_moves_urllib_parse, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+- "moves.urllib_parse", "moves.urllib.parse")
+-
+-
+-class Module_six_moves_urllib_error(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_error"""
+-
+-
+-_urllib_error_moved_attributes = [
+- MovedAttribute("URLError", "urllib2", "urllib.error"),
+- MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+- MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+-]
+-for attr in _urllib_error_moved_attributes:
+- setattr(Module_six_moves_urllib_error, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+- "moves.urllib_error", "moves.urllib.error")
+-
+-
+-class Module_six_moves_urllib_request(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_request"""
+-
+-
+-_urllib_request_moved_attributes = [
+- MovedAttribute("urlopen", "urllib2", "urllib.request"),
+- MovedAttribute("install_opener", "urllib2", "urllib.request"),
+- MovedAttribute("build_opener", "urllib2", "urllib.request"),
+- MovedAttribute("pathname2url", "urllib", "urllib.request"),
+- MovedAttribute("url2pathname", "urllib", "urllib.request"),
+- MovedAttribute("getproxies", "urllib", "urllib.request"),
+- MovedAttribute("Request", "urllib2", "urllib.request"),
+- MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+- MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+- MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+- MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+- MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+- MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+- MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+- MovedAttribute("URLopener", "urllib", "urllib.request"),
+- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+- MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+-]
+-for attr in _urllib_request_moved_attributes:
+- setattr(Module_six_moves_urllib_request, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+- "moves.urllib_request", "moves.urllib.request")
+-
+-
+-class Module_six_moves_urllib_response(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_response"""
+-
+-
+-_urllib_response_moved_attributes = [
+- MovedAttribute("addbase", "urllib", "urllib.response"),
+- MovedAttribute("addclosehook", "urllib", "urllib.response"),
+- MovedAttribute("addinfo", "urllib", "urllib.response"),
+- MovedAttribute("addinfourl", "urllib", "urllib.response"),
+-]
+-for attr in _urllib_response_moved_attributes:
+- setattr(Module_six_moves_urllib_response, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+- "moves.urllib_response", "moves.urllib.response")
+-
+-
+-class Module_six_moves_urllib_robotparser(_LazyModule):
+-
+- """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+-
+-
+-_urllib_robotparser_moved_attributes = [
+- MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+-]
+-for attr in _urllib_robotparser_moved_attributes:
+- setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+-del attr
+-
+-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+-
+-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+- "moves.urllib_robotparser", "moves.urllib.robotparser")
+-
+-
+-class Module_six_moves_urllib(types.ModuleType):
+-
+- """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+- __path__ = [] # mark as package
+- parse = _importer._get_module("moves.urllib_parse")
+- error = _importer._get_module("moves.urllib_error")
+- request = _importer._get_module("moves.urllib_request")
+- response = _importer._get_module("moves.urllib_response")
+- robotparser = _importer._get_module("moves.urllib_robotparser")
+-
+- def __dir__(self):
+- return ['parse', 'error', 'request', 'response', 'robotparser']
+-
+-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+- "moves.urllib")
+-
+-
+-def add_move(move):
+- """Add an item to six.moves."""
+- setattr(_MovedItems, move.name, move)
+-
+-
+-def remove_move(name):
+- """Remove item from six.moves."""
+- try:
+- delattr(_MovedItems, name)
+- except AttributeError:
+- try:
+- del moves.__dict__[name]
+- except KeyError:
+- raise AttributeError("no such move, %r" % (name,))
+-
+-
+-if PY3:
+- _meth_func = "__func__"
+- _meth_self = "__self__"
+-
+- _func_closure = "__closure__"
+- _func_code = "__code__"
+- _func_defaults = "__defaults__"
+- _func_globals = "__globals__"
+-else:
+- _meth_func = "im_func"
+- _meth_self = "im_self"
+-
+- _func_closure = "func_closure"
+- _func_code = "func_code"
+- _func_defaults = "func_defaults"
+- _func_globals = "func_globals"
+-
+-
+-try:
+- advance_iterator = next
+-except NameError:
+- def advance_iterator(it):
+- return it.next()
+-next = advance_iterator
+-
+-
+-try:
+- callable = callable
+-except NameError:
+- def callable(obj):
+- return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+-
+-
+-if PY3:
+- def get_unbound_function(unbound):
+- return unbound
+-
+- create_bound_method = types.MethodType
+-
+- def create_unbound_method(func, cls):
+- return func
+-
+- Iterator = object
+-else:
+- def get_unbound_function(unbound):
+- return unbound.im_func
+-
+- def create_bound_method(func, obj):
+- return types.MethodType(func, obj, obj.__class__)
+-
+- def create_unbound_method(func, cls):
+- return types.MethodType(func, None, cls)
+-
+- class Iterator(object):
+-
+- def next(self):
+- return type(self).__next__(self)
+-
+- callable = callable
+-_add_doc(get_unbound_function,
+- """Get the function out of a possibly unbound function""")
+-
+-
+-get_method_function = operator.attrgetter(_meth_func)
+-get_method_self = operator.attrgetter(_meth_self)
+-get_function_closure = operator.attrgetter(_func_closure)
+-get_function_code = operator.attrgetter(_func_code)
+-get_function_defaults = operator.attrgetter(_func_defaults)
+-get_function_globals = operator.attrgetter(_func_globals)
+-
+-
+-if PY3:
+- def iterkeys(d, **kw):
+- return iter(d.keys(**kw))
+-
+- def itervalues(d, **kw):
+- return iter(d.values(**kw))
+-
+- def iteritems(d, **kw):
+- return iter(d.items(**kw))
+-
+- def iterlists(d, **kw):
+- return iter(d.lists(**kw))
+-
+- viewkeys = operator.methodcaller("keys")
+-
+- viewvalues = operator.methodcaller("values")
+-
+- viewitems = operator.methodcaller("items")
+-else:
+- def iterkeys(d, **kw):
+- return d.iterkeys(**kw)
+-
+- def itervalues(d, **kw):
+- return d.itervalues(**kw)
+-
+- def iteritems(d, **kw):
+- return d.iteritems(**kw)
+-
+- def iterlists(d, **kw):
+- return d.iterlists(**kw)
+-
+- viewkeys = operator.methodcaller("viewkeys")
+-
+- viewvalues = operator.methodcaller("viewvalues")
+-
+- viewitems = operator.methodcaller("viewitems")
+-
+-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+-_add_doc(iteritems,
+- "Return an iterator over the (key, value) pairs of a dictionary.")
+-_add_doc(iterlists,
+- "Return an iterator over the (key, [values]) pairs of a dictionary.")
+-
+-
+-if PY3:
+- def b(s):
+- return s.encode("latin-1")
+-
+- def u(s):
+- return s
+- unichr = chr
+- import struct
+- int2byte = struct.Struct(">B").pack
+- del struct
+- byte2int = operator.itemgetter(0)
+- indexbytes = operator.getitem
+- iterbytes = iter
+- import io
+- StringIO = io.StringIO
+- BytesIO = io.BytesIO
+- _assertCountEqual = "assertCountEqual"
+- if sys.version_info[1] <= 1:
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+- else:
+- _assertRaisesRegex = "assertRaisesRegex"
+- _assertRegex = "assertRegex"
+-else:
+- def b(s):
+- return s
+- # Workaround for standalone backslash
+-
+- def u(s):
+- return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+- unichr = unichr
+- int2byte = chr
+-
+- def byte2int(bs):
+- return ord(bs[0])
+-
+- def indexbytes(buf, i):
+- return ord(buf[i])
+- iterbytes = functools.partial(itertools.imap, ord)
+- import StringIO
+- StringIO = BytesIO = StringIO.StringIO
+- _assertCountEqual = "assertItemsEqual"
+- _assertRaisesRegex = "assertRaisesRegexp"
+- _assertRegex = "assertRegexpMatches"
+-_add_doc(b, """Byte literal""")
+-_add_doc(u, """Text literal""")
+-
+-
+-def assertCountEqual(self, *args, **kwargs):
+- return getattr(self, _assertCountEqual)(*args, **kwargs)
+-
+-
+-def assertRaisesRegex(self, *args, **kwargs):
+- return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+-
+-
+-def assertRegex(self, *args, **kwargs):
+- return getattr(self, _assertRegex)(*args, **kwargs)
+-
+-
+-if PY3:
+- exec_ = getattr(moves.builtins, "exec")
+-
+- def reraise(tp, value, tb=None):
+- if value is None:
+- value = tp()
+- if value.__traceback__ is not tb:
+- raise value.with_traceback(tb)
+- raise value
+-
+-else:
+- def exec_(_code_, _globs_=None, _locs_=None):
+- """Execute code in a namespace."""
+- if _globs_ is None:
+- frame = sys._getframe(1)
+- _globs_ = frame.f_globals
+- if _locs_ is None:
+- _locs_ = frame.f_locals
+- del frame
+- elif _locs_ is None:
+- _locs_ = _globs_
+- exec("""exec _code_ in _globs_, _locs_""")
+-
+- exec_("""def reraise(tp, value, tb=None):
+- raise tp, value, tb
+-""")
+-
+-
+-if sys.version_info[:2] == (3, 2):
+- exec_("""def raise_from(value, from_value):
+- if from_value is None:
+- raise value
+- raise value from from_value
+-""")
+-elif sys.version_info[:2] > (3, 2):
+- exec_("""def raise_from(value, from_value):
+- raise value from from_value
+-""")
+-else:
+- def raise_from(value, from_value):
+- raise value
+-
+-
+-print_ = getattr(moves.builtins, "print", None)
+-if print_ is None:
+- def print_(*args, **kwargs):
+- """The new-style print function for Python 2.4 and 2.5."""
+- fp = kwargs.pop("file", sys.stdout)
+- if fp is None:
+- return
+-
+- def write(data):
+- if not isinstance(data, basestring):
+- data = str(data)
+- # If the file has an encoding, encode unicode with it.
+- if (isinstance(fp, file) and
+- isinstance(data, unicode) and
+- fp.encoding is not None):
+- errors = getattr(fp, "errors", None)
+- if errors is None:
+- errors = "strict"
+- data = data.encode(fp.encoding, errors)
+- fp.write(data)
+- want_unicode = False
+- sep = kwargs.pop("sep", None)
+- if sep is not None:
+- if isinstance(sep, unicode):
+- want_unicode = True
+- elif not isinstance(sep, str):
+- raise TypeError("sep must be None or a string")
+- end = kwargs.pop("end", None)
+- if end is not None:
+- if isinstance(end, unicode):
+- want_unicode = True
+- elif not isinstance(end, str):
+- raise TypeError("end must be None or a string")
+- if kwargs:
+- raise TypeError("invalid keyword arguments to print()")
+- if not want_unicode:
+- for arg in args:
+- if isinstance(arg, unicode):
+- want_unicode = True
+- break
+- if want_unicode:
+- newline = unicode("\n")
+- space = unicode(" ")
+- else:
+- newline = "\n"
+- space = " "
+- if sep is None:
+- sep = space
+- if end is None:
+- end = newline
+- for i, arg in enumerate(args):
+- if i:
+- write(sep)
+- write(arg)
+- write(end)
+-if sys.version_info[:2] < (3, 3):
+- _print = print_
+-
+- def print_(*args, **kwargs):
+- fp = kwargs.get("file", sys.stdout)
+- flush = kwargs.pop("flush", False)
+- _print(*args, **kwargs)
+- if flush and fp is not None:
+- fp.flush()
+-
+-_add_doc(reraise, """Reraise an exception.""")
+-
+-if sys.version_info[0:2] < (3, 4):
+- def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+- updated=functools.WRAPPER_UPDATES):
+- def wrapper(f):
+- f = functools.wraps(wrapped, assigned, updated)(f)
+- f.__wrapped__ = wrapped
+- return f
+- return wrapper
+-else:
+- wraps = functools.wraps
+-
+-
+-def with_metaclass(meta, *bases):
+- """Create a base class with a metaclass."""
+- # This requires a bit of explanation: the basic idea is to make a dummy
+- # metaclass for one level of class instantiation that replaces itself with
+- # the actual metaclass.
+- class metaclass(meta):
+-
+- def __new__(cls, name, this_bases, d):
+- return meta(name, bases, d)
+- return type.__new__(metaclass, 'temporary_class', (), {})
+-
+-
+-def add_metaclass(metaclass):
+- """Class decorator for creating a class with a metaclass."""
+- def wrapper(cls):
+- orig_vars = cls.__dict__.copy()
+- slots = orig_vars.get('__slots__')
+- if slots is not None:
+- if isinstance(slots, str):
+- slots = [slots]
+- for slots_var in slots:
+- orig_vars.pop(slots_var)
+- orig_vars.pop('__dict__', None)
+- orig_vars.pop('__weakref__', None)
+- return metaclass(cls.__name__, cls.__bases__, orig_vars)
+- return wrapper
+-
+-
+-def python_2_unicode_compatible(klass):
+- """
+- A decorator that defines __unicode__ and __str__ methods under Python 2.
+- Under Python 3 it does nothing.
+-
+- To support Python 2 and 3 with a single code base, define a __str__ method
+- returning text and apply this decorator to the class.
+- """
+- if PY2:
+- if '__str__' not in klass.__dict__:
+- raise ValueError("@python_2_unicode_compatible cannot be applied "
+- "to %s because it doesn't define __str__()." %
+- klass.__name__)
+- klass.__unicode__ = klass.__str__
+- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+- return klass
+-
+-
+-# Complete the moves implementation.
+-# This code is at the end of this module to speed up module loading.
+-# Turn this module into a package.
+-__path__ = [] # required for PEP 302 and PEP 451
+-__package__ = __name__ # see PEP 366 @ReservedAssignment
+-if globals().get("__spec__") is not None:
+- __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+-# Remove other six meta path importers, since they cause problems. This can
+-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+-# this for some reason.)
+-if sys.meta_path:
+- for i, importer in enumerate(sys.meta_path):
+- # Here's some real nastiness: Another "instance" of the six module might
+- # be floating around. Therefore, we can't use isinstance() to check for
+- # the six meta path importer, since the other six instance will have
+- # inserted an importer with different class.
+- if (type(importer).__name__ == "_SixMetaPathImporter" and
+- importer.name == __name__):
+- del sys.meta_path[i]
+- break
+- del i, importer
+-# Finally, add the importer to the meta path import hook.
+-sys.meta_path.append(_importer)
+diff --git a/tasks/docs.py b/tasks/docs.py
+index 3360279..77c1d83 100644
+--- a/tasks/docs.py
++++ b/tasks/docs.py
+@@ -11,7 +11,8 @@ from invoke.util import cd
+ from path import Path
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs
+
+
+ # -----------------------------------------------------------------------------
+@@ -69,6 +70,7 @@ def build(ctx, builder="html", language=None, options=""):
+ opts=options)
+ ctx.run(command)
+
++
+ @task(help={
+ "builder": "Builder to use (html, ...)",
+ "language": "Language to use (en, ...)",
+@@ -81,12 +83,38 @@ def rebuild(ctx, builder="html", language=None, options=""):
+ clean(ctx)
+ build(ctx, builder=builder, language=None, options=options)
+
++
++@task(aliases=["auto", "watch"],
++ help={
++ "builder": "Builder to use (html, ...)",
++ "language": "Language to use (en, ...)",
++ "options": "Additional options for sphinx-build",
++})
++def autobuild(ctx, builder="html", language=None, options=""):
++ """Build docs with sphinx-build"""
++ language = _sphinxdoc_get_language(ctx, language)
++ sourcedir = ctx.config.sphinx.sourcedir
++ destdir = _sphinxdoc_get_destdir(ctx, builder, language=language)
++ destdir = destdir.abspath()
++ with cd(sourcedir):
++ destdir_relative = Path(".").relpathto(destdir)
++ command = "sphinx-autobuild {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \
++ .format(builder=builder, sourcedir=".",
++ destdir=destdir_relative,
++ language=language,
++ opts=options)
++ ctx.run(command)
++
++
+ @task
+ def linkcheck(ctx):
+ """Check if all links are corect."""
+ build(ctx, builder="linkcheck")
+
+-@task(help={"language": "Language to use (en, ...)"})
++
++@task(aliases=["open"],
++ help={"language": "Language to use (en, ...)"}
++)
+ def browse(ctx, language=None):
+ """Open documentation in web browser."""
+ output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language)
+@@ -182,6 +210,7 @@ def update_translation(ctx, language="all"):
+ # -----------------------------------------------------------------------------
+ namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation)
+ namespace.add_task(build, default=True)
++namespace.add_task(autobuild)
+ namespace.configure({
+ "sphinx": {
+ # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ...
+diff --git a/tasks/invoke_cleanup.py b/tasks/invoke_cleanup.py
+new file mode 100644
+index 0000000..4e631c4
+--- /dev/null
++++ b/tasks/invoke_cleanup.py
+@@ -0,0 +1,447 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides cleanup tasks for invoke build scripts (as generic invoke tasklet).
++Simplifies writing common, composable and extendable cleanup tasks.
++
++PYTHON PACKAGE DEPENDENCIES:
++
++* path (python >= 3.5) or path.py >= 11.5.0 (as path-object abstraction)
++* pathlib (for ant-like wildcard patterns; since: python > 3.5)
++* pycmd (required-by: clean_python())
++
++
++cleanup task: Add Additional Directories and Files to be removed
++-------------------------------------------------------------------------------
++
++Create an invoke configuration file (YAML of JSON) with the additional
++configuration data:
++
++.. code-block:: yaml
++
++ # -- FILE: invoke.yaml
++ # USE: cleanup.directories, cleanup.files to override current configuration.
++ cleanup:
++ # directories: Default directory patterns (can be overwritten).
++ # files: Default file patterns (can be ovewritten).
++ extra_directories:
++ - **/tmp/
++ extra_files:
++ - **/*.log
++ - **/*.bak
++
++
++Registration of Cleanup Tasks
++------------------------------
++
++Other task modules often have an own cleanup task to recover the clean state.
++The :meth:`cleanup` task, that is provided here, supports the registration
++of additional cleanup tasks. Therefore, when the :meth:`cleanup` task is executed,
++all registered cleanup tasks will be executed.
++
++EXAMPLE::
++
++ # -- FILE: tasks/docs.py
++ from __future__ import absolute_import
++ from invoke import task, Collection
++ from invoke_cleanup import cleanup_tasks, cleanup_dirs
++
++ @task
++ def clean(ctx):
++ "Cleanup generated documentation artifacts."
++ dry_run = ctx.config.run.dry
++ cleanup_dirs(["build/docs"], dry_run=dry_run)
++
++ namespace = Collection(clean)
++ ...
++
++ # -- REGISTER CLEANUP TASK:
++ cleanup_tasks.add_task(clean, "clean_docs")
++ cleanup_tasks.configure(namespace.configuration())
++"""
++
++from __future__ import absolute_import, print_function
++import os
++import sys
++from invoke import task, Collection
++from invoke.executor import Executor
++from invoke.exceptions import Exit, Failure, UnexpectedExit
++from invoke.util import cd
++from path import Path
++
++# -- PYTHON BACKWARD COMPATIBILITY:
++python_version = sys.version_info[:2]
++python35 = (3, 5) # HINT: python3.8 does not raise OSErrors.
++if python_version < python35: # noqa
++ import pathlib2 as pathlib
++else:
++ import pathlib # noqa
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++VERSION = "0.3.6"
++
++
++# -----------------------------------------------------------------------------
++# CLEANUP UTILITIES:
++# -----------------------------------------------------------------------------
++def execute_cleanup_tasks(ctx, cleanup_tasks, workdir=".", verbose=False):
++ """Execute several cleanup tasks as part of the cleanup.
++
++ :param ctx: Context object for the tasks.
++ :param cleanup_tasks: Collection of cleanup tasks (as Collection).
++ """
++ # pylint: disable=redefined-outer-name
++ executor = Executor(cleanup_tasks, ctx.config)
++ failure_count = 0
++ with cd(workdir) as cwd:
++ for cleanup_task in cleanup_tasks.tasks:
++ try:
++ print("CLEANUP TASK: %s" % cleanup_task)
++ executor.execute(cleanup_task)
++ except (Exit, Failure, UnexpectedExit) as e:
++ print(e)
++ print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task)
++ failure_count += 1
++
++ if failure_count:
++ print("CLEANUP TASKS: %d failure(s) occured" % failure_count)
++
++
++def make_excluded(excluded, config_dir=None, workdir=None):
++ workdir = workdir or Path.getcwd()
++ config_dir = config_dir or workdir
++ workdir = Path(workdir)
++ config_dir = Path(config_dir)
++
++ excluded2 = []
++ for p in excluded:
++ assert p, "REQUIRE: non-empty"
++ p = Path(p)
++ if p.isabs():
++ excluded2.append(p.normpath())
++ else:
++ # -- RELATIVE PATH:
++ # Described relative to config_dir.
++ # Recompute it relative to current workdir.
++ p = Path(config_dir)/p
++ p = workdir.relpathto(p)
++ excluded2.append(p.normpath())
++ excluded2.append(p.abspath())
++ return set(excluded2)
++
++
++def is_directory_excluded(directory, excluded):
++ directory = Path(directory).normpath()
++ directory2 = directory.abspath()
++ if (directory in excluded) or (directory2 in excluded):
++ return True
++ # -- OTHERWISE:
++ return False
++
++
++def cleanup_dirs(patterns, workdir=".", excluded=None,
++ dry_run=False, verbose=False, show_skipped=False):
++ """Remove directories (and their contents) recursively.
++ Skips removal if directories does not exist.
++
++ :param patterns: Directory name patterns, like "**/tmp*" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ excluded = excluded or []
++ excluded = set([Path(p) for p in excluded])
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ warn2_counter = 0
++ for dir_pattern in patterns:
++ for directory in path_glob(dir_pattern, current_dir):
++ if is_directory_excluded(directory, excluded):
++ print("SKIP-DIR: %s (excluded)" % directory)
++ continue
++ directory2 = directory.abspath()
++ if sys.executable.startswith(directory2):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # pylint: disable=line-too-long
++ print("SKIP-SUICIDE: '%s' contains current python executable" % directory)
++ continue
++ elif directory2.startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ # HINT: Limit noise in DIAGNOSTIC OUTPUT to X messages.
++ if warn2_counter <= 4: # noqa
++ print("SKIP-SUICIDE: '%s'" % directory)
++ warn2_counter += 1
++ continue
++
++ if not directory.isdir():
++ if show_skipped:
++ print("RMTREE: %s (SKIPPED: Not a directory)" % directory)
++ continue
++
++ if dry_run:
++ print("RMTREE: %s (dry-run)" % directory)
++ else:
++ try:
++ # -- MAYBE: directory.rmtree(ignore_errors=True)
++ print("RMTREE: %s" % directory)
++ directory.rmtree_p()
++ except OSError as e:
++ print("RMTREE-FAILED: %s (for: %s)" % (e, directory))
++
++
++def cleanup_files(patterns, workdir=".", dry_run=False, verbose=False, show_skipped=False):
++ """Remove files or files selected by file patterns.
++ Skips removal if file does not exist.
++
++ :param patterns: File patterns, like "**/*.pyc" (as list).
++ :param workdir: Current work directory (default=".")
++ :param dry_run: Dry-run mode indicator (as bool).
++ """
++ show_skipped = show_skipped or verbose
++ current_dir = Path(workdir)
++ python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath()
++ error_message = None
++ error_count = 0
++ for file_pattern in patterns:
++ for file_ in path_glob(file_pattern, current_dir):
++ if file_.abspath().startswith(python_basedir):
++ # -- PROTECT VIRTUAL ENVIRONMENT (currently in use):
++ continue
++ if not file_.isfile():
++ if show_skipped:
++ print("REMOVE: %s (SKIPPED: Not a file)" % file_)
++ continue
++
++ if dry_run:
++ print("REMOVE: %s (dry-run)" % file_)
++ else:
++ print("REMOVE: %s" % file_)
++ try:
++ file_.remove_p()
++ except os.error as e:
++ message = "%s: %s" % (e.__class__.__name__, e)
++ print(message + " basedir: "+ python_basedir)
++ error_count += 1
++ if not error_message:
++ error_message = message
++ if False and error_message: # noqa
++ class CleanupError(RuntimeError):
++ pass
++ raise CleanupError(error_message)
++
++
++def path_glob(pattern, current_dir=None):
++ """Use pathlib for ant-like patterns, like: "**/*.py"
++
++ :param pattern: File/directory pattern to use (as string).
++ :param current_dir: Current working directory (as Path, pathlib.Path, str)
++ :return Resolved Path (as path.Path).
++ """
++ if not current_dir: # noqa
++ current_dir = pathlib.Path.cwd()
++ elif not isinstance(current_dir, pathlib.Path):
++ # -- CASE: string, path.Path (string-like)
++ current_dir = pathlib.Path(str(current_dir))
++
++ pattern_path = Path(pattern)
++ if pattern_path.isabs():
++ # -- SPECIAL CASE: Path.glob() only supports relative-path(s) / pattern(s).
++ if pattern_path.isdir():
++ yield pattern_path
++ return
++
++ # -- HINT: OSError is no longer raised in pathlib2 or python35.pathlib
++ # try:
++ for p in current_dir.glob(pattern):
++ yield Path(str(p))
++ # except OSError as e:
++ # # -- CORNER-CASE 1: x.glob(pattern) may fail with:
++ # # OSError: [Errno 13] Permission denied: <filename>
++ # # HINT: Directory lacks excutable permissions for traversal.
++ # # -- CORNER-CASE 2: symlinked endless loop
++ # # OSError: [Errno 62] Too many levels of symbolic links: <filename>
++ # print("{0}: {1}".format(e.__class__.__name__, e))
++
++
++# -----------------------------------------------------------------------------
++# GENERIC CLEANUP TASKS:
++# -----------------------------------------------------------------------------
++@task(help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean(ctx, workdir=".", verbose=False):
++ """Cleanup temporary dirs/files to regain a clean state."""
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup.directories or [])
++ directories.extend(ctx.config.cleanup.extra_directories or [])
++ files = list(ctx.config.cleanup.files or [])
++ files.extend(ctx.config.cleanup.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ execute_cleanup_tasks(ctx, cleanup_tasks)
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python = ctx.config.cleanup.use_cleanup_python or False
++ # if use_cleanup_python:
++ # clean_python(ctx)
++
++
++@task(name="all", aliases=("distclean",),
++ help={
++ "workdir": "Directory to clean(up) (default: $CWD).",
++ "verbose": "Enable verbose mode (default: OFF).",
++})
++def clean_all(ctx, workdir=".", verbose=False):
++ """Clean up everything, even the precious stuff.
++ NOTE: clean task is executed last.
++ """
++ dry_run = ctx.config.run.dry
++ config_dir = getattr(ctx.config, "config_dir", workdir)
++ directories = list(ctx.config.cleanup_all.directories or [])
++ directories.extend(ctx.config.cleanup_all.extra_directories or [])
++ files = list(ctx.config.cleanup_all.files or [])
++ files.extend(ctx.config.cleanup_all.extra_files or [])
++ excluded_directories = list(ctx.config.cleanup_all.excluded_directories or [])
++ excluded_directories.extend(ctx.config.cleanup.excluded_directories or [])
++ excluded_directories = make_excluded(excluded_directories,
++ config_dir=config_dir, workdir=".")
++
++ # -- PERFORM CLEANUP:
++ # HINT: Remove now directories, files first before cleanup-tasks.
++ cleanup_dirs(directories, workdir=workdir, excluded=excluded_directories,
++ dry_run=dry_run, verbose=verbose)
++ cleanup_files(files, workdir=workdir, dry_run=dry_run, verbose=verbose)
++ execute_cleanup_tasks(ctx, cleanup_all_tasks)
++ clean(ctx, workdir=workdir, verbose=verbose)
++
++ # -- CONFIGURABLE EXTENSION-POINT:
++ # use_cleanup_python1 = ctx.config.cleanup.use_cleanup_python or False
++ # use_cleanup_python2 = ctx.config.cleanup_all.use_cleanup_python or False
++ # if use_cleanup_python2 and not use_cleanup_python1:
++ # clean_python(ctx)
++
++
++@task(aliases=["python"])
++def clean_python(ctx, workdir=".", verbose=False):
++ """Cleanup python related files/dirs: *.pyc, *.pyo, ..."""
++ dry_run = ctx.config.run.dry or False
++ # MAYBE NOT: "**/__pycache__"
++ cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++ if not dry_run:
++ ctx.run("py.cleanup")
++ cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"],
++ workdir=workdir, dry_run=dry_run, verbose=verbose)
++
++
++@task(help={
++ "path": "Path to cleanup.",
++ "interactive": "Enable interactive mode.",
++ "force": "Enable force mode.",
++ "options": "Additional git-clean options",
++})
++def git_clean(ctx, path=None, interactive=False, force=False,
++ dry_run=False, options=None):
++ """Perform git-clean command to cleanup the worktree of a git repository.
++
++ BEWARE: This may remove any precious files that are not checked in.
++ WARNING: DANGEROUS COMMAND.
++ """
++ args = []
++ force = force or ctx.config.git_clean.force
++ path = path or ctx.config.git_clean.path or "."
++ interactive = interactive or ctx.config.git_clean.interactive
++ dry_run = dry_run or ctx.config.run.dry or ctx.config.git_clean.dry_run
++
++ if interactive:
++ args.append("--interactive")
++ if force:
++ args.append("--force")
++ if dry_run:
++ args.append("--dry-run")
++ args.append(options or "")
++ args = " ".join(args).strip()
++
++ ctx.run("git clean {options} {path}".format(options=args, path=path))
++
++
++# -----------------------------------------------------------------------------
++# TASK CONFIGURATION:
++# -----------------------------------------------------------------------------
++CLEANUP_EMPTY_CONFIG = {
++ "directories": [],
++ "files": [],
++ "extra_directories": [],
++ "extra_files": [],
++ "excluded_directories": [],
++ "excluded_files": [],
++ "use_cleanup_python": False,
++}
++def make_cleanup_config(**kwargs):
++ config_data = CLEANUP_EMPTY_CONFIG.copy()
++ config_data.update(kwargs)
++ return config_data
++
++
++namespace = Collection(clean_all, clean_python)
++namespace.add_task(clean, default=True)
++namespace.add_task(git_clean)
++namespace.configure({
++ "cleanup": make_cleanup_config(
++ files=["**/*.bak", "**/*.log", "**/*.tmp", "**/.DS_Store"],
++ excluded_directories=[".git", ".hg", ".bzr", ".svn"],
++ ),
++ "cleanup_all": make_cleanup_config(
++ directories=[".venv*", ".tox", "downloads", "tmp"],
++ ),
++ "git_clean": {
++ "interactive": True,
++ "force": False,
++ "path": ".",
++ "dry_run": False,
++ },
++})
++
++
++# -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task)
++# NOTE: Can be used by other tasklets to register cleanup tasks.
++cleanup_tasks = Collection("cleanup_tasks")
++cleanup_all_tasks = Collection("cleanup_all_tasks")
++
++# -- EXTEND NORMAL CLEANUP-TASKS:
++# DISABLED: cleanup_tasks.add_task(clean_python)
++
++# -----------------------------------------------------------------------------
++# EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules
++# -----------------------------------------------------------------------------
++def config_add_cleanup_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup"]["files"]
++ the_cleanup_files.extend(files)
++ # namespace.configure({"cleanup": {"files": files}})
++ # print("DIAG cleanup.config.cleanup: %r" % namespace.configuration())
++
++def config_add_cleanup_all_dirs(directories):
++ # pylint: disable=protected-access
++ the_cleanup_directories = namespace._configuration["cleanup_all"]["directories"]
++ the_cleanup_directories.extend(directories)
++
++def config_add_cleanup_all_files(files):
++ # pylint: disable=protected-access
++ the_cleanup_files = namespace._configuration["cleanup_all"]["files"]
++ the_cleanup_files.extend(files)
+diff --git a/tasks/py.requirements.txt b/tasks/py.requirements.txt
+index 9c82d11..ac19e94 100644
+--- a/tasks/py.requirements.txt
++++ b/tasks/py.requirements.txt
+@@ -13,8 +13,8 @@ pycmd
+ six==1.15.0
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+-path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++path.py >= 11.5.0; python_version < '3.5'
+
+ # -- PYTHON2 BACKPORTS:
+ pathlib; python_version <= '3.4'
+diff --git a/tasks/release.py b/tasks/release.py
+index dba85c8..e17a46f 100644
+--- a/tasks/release.py
++++ b/tasks/release.py
+@@ -51,7 +51,7 @@ Configuration file for pypi repositories:
+
+ from __future__ import absolute_import, print_function
+ from invoke import Collection, task
+-from ._tasklet_cleanup import path_glob
++from .invoke_cleanup import path_glob
+ from ._dry_run import DryRunContext
+
+
+diff --git a/tasks/test.py b/tasks/test.py
+index bfa2d80..d6b4189 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -9,7 +9,8 @@ import sys
+ from invoke import task, Collection
+
+ # -- TASK-LIBRARY:
+-from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++# PREPARED: from invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
++from .invoke_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
new file mode 100644
index 000000000..8dc6fb95f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
@@ -0,0 +1,36 @@
+From 281a4f6befca3817adad24ebc9217655ce1ee990 Mon Sep 17 00:00:00 2001
+From: Daniel Lemm <61800298+ffe4@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 11:27:10 +0200
+Subject: [PATCH] Docs: change code blocks from bash to console
+
+---
+ README.rst | 2 +-
+ docs/practical_tips.rst | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index 4a905ab..22b0352 100644
+--- a/README.rst
++++ b/README.rst
+@@ -76,7 +76,7 @@ In that directory create a file called "example_steps.py" containing:
+
+ Run behave:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave
+ Feature: Showing off behave # features/example.feature:2
+diff --git a/docs/practical_tips.rst b/docs/practical_tips.rst
+index b70569f..75bc736 100644
+--- a/docs/practical_tips.rst
++++ b/docs/practical_tips.rst
+@@ -30,7 +30,7 @@ For example, if you want to use the feature files in the same directory for
+ testing the model layer and the UI layer, this can be done by using the
+ ``--stage`` option, like with:
+
+-.. code-block:: bash
++.. code-block:: console
+
+ $ behave --stage=model features/
+ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features.
diff --git a/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
new file mode 100644
index 000000000..03a4bb7a4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
@@ -0,0 +1,24 @@
+From 895f6c9029ee9b853377520610465841e330671e Mon Sep 17 00:00:00 2001
+From: Alex McLarty <alexjmclarty@gmail.com>
+Date: Fri, 12 Jul 2019 08:22:31 +0100
+Subject: [PATCH] Fix typo in tutorial
+
+---
+ docs/tutorial.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/docs/tutorial.rst b/docs/tutorial.rst
+index 04b2f63..27698a4 100644
+--- a/docs/tutorial.rst
++++ b/docs/tutorial.rst
+@@ -157,8 +157,8 @@ basic actions. You may use a Scenario Outline to achieve this:
+
+ Scenario Outline: Blenders
+ Given I put <thing> in a blender,
+- when I switch the blender on
+- then it should transform into <other thing>
++ When I switch the blender on
++ Then it should transform into <other thing>
+
+ Examples: Amphibians
+ | thing | other thing |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
new file mode 100644
index 000000000..26059047f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
@@ -0,0 +1,80 @@
+From c9ba7f664107b5c8b263fea29fd4823f72536505 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 1 Dec 2020 23:19:51 +0100
+Subject: [PATCH] py.requirements: Use PyHamcrest < 2.0 for python2.7
+
+---
+ issue.features/py.requirements.txt | 3 ++-
+ py.requirements/ci.tox.txt | 6 ++++--
+ py.requirements/ci.travis.txt | 7 +++++--
+ py.requirements/testing.txt | 6 ++++--
+ 4 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/issue.features/py.requirements.txt b/issue.features/py.requirements.txt
+index 6e3cf83..f8a2f8d 100644
+--- a/issue.features/py.requirements.txt
++++ b/issue.features/py.requirements.txt
+@@ -8,4 +8,5 @@
+ #
+ # ============================================================================
+
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 20ae791..5bee524 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -4,9 +4,11 @@
+
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index c69445c..372116a 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,11 +1,14 @@
+ # ============================================================================
+ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- ci.travis.txt
+ # ============================================================================
++
+ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index d3bca18..fc8fd82 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -6,9 +6,11 @@
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+-pytest-html >= 1.19.0
++
++pytest-html >= 1.19.0,<2.0
+ mock >= 2.0
+-PyHamcrest == 2.0.2
++PyHamcrest >= 2.0.2; python_version >= '3.0'
++PyHamcrest < 2.0; python_version < '3.0'
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
diff --git a/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
new file mode 100644
index 000000000..7d28aff52
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
@@ -0,0 +1,21 @@
+From 0586024620c67c1c85a6a5a0783154d1d2434127 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 5 Dec 2020 00:33:22 +0100
+Subject: [PATCH] UPDATE: PR #877 was merged.
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d758364..4e20bb8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -58,6 +58,7 @@ MINOR:
+
+ DOCUMENTATION:
+
++* pull #877: docs: API reference - Capitalizing Step Keywords in example (provided by: Ibrian93)
+ * pull #731: Update links to Django docs (provided by: bittner)
+ * pull #722: DOC remove remaining pythonhosted links (provided by: leszekhanusz)
+ * pull #701: behave/runner.py docstrings (provided by: spitGlued)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
new file mode 100644
index 000000000..700aec6be
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
@@ -0,0 +1,28 @@
+From 532924d3528226217460485d583d9888047f1bb5 Mon Sep 17 00:00:00 2001
+From: Brian Icochea <ibrian93@gmail.com>
+Date: Sun, 15 Nov 2020 18:35:16 +0100
+Subject: [PATCH] capitalizing steps
+
+---
+ docs/api.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/api.rst b/docs/api.rst
+index 4763ad6..7463863 100644
+--- a/docs/api.rst
++++ b/docs/api.rst
+@@ -74,10 +74,10 @@ the name of their preceding keyword, so given the following feature file:
+ .. code-block:: gherkin
+
+ Given some known state
+- and some other known state
+- when some action is taken
+- then some outcome is observed
+- but some other outcome is not observed.
++ And some other known state
++ When some action is taken
++ Then some outcome is observed
++ But some other outcome is not observed.
+
+ the first "and" step will be renamed internally to "given" and *behave*
+ will look for a step implementation decorated with either "given" or "step":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
new file mode 100644
index 000000000..cb46522ba
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
@@ -0,0 +1,56 @@
+From fd17cddb8216905574643146afb91625aa939de6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 11 Dec 2020 20:51:44 +0100
+Subject: [PATCH] FIX: invoke-task develop.update-gherkin that aborted after
+ diff
+
+* Show now if "gherkin-languages.json" does not change
+* Add --verbose option to show diff output if file has changed
+---
+ tasks/develop.py | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/tasks/develop.py b/tasks/develop.py
+index 9a21363..eb5fedd 100644
+--- a/tasks/develop.py
++++ b/tasks/develop.py
+@@ -9,6 +9,7 @@ from invoke.util import cd
+ from path import Path
+ import requests
+
++
+ # -----------------------------------------------------------------------------
+ # CONSTANTS:
+ # -----------------------------------------------------------------------------
+@@ -18,8 +19,8 @@ GHERKIN_LANGUAGES_URL = "https://raw.githubusercontent.com/cucumber/cucumber/mas
+ # -----------------------------------------------------------------------------
+ # TASKS:
+ # -----------------------------------------------------------------------------
+-@task
+-def update_gherkin(ctx, dry_run=False):
++@task(aliases=["update-languages"])
++def update_gherkin(ctx, dry_run=False, verbose=False):
+ """Update "gherkin-languages.json" file from cucumber-repo.
+
+ * Download "gherkin-languages.json" from cucumber repo
+@@ -41,8 +42,18 @@ def update_gherkin(ctx, dry_run=False):
+
+ print('Generating "i18n.py" ...')
+ ctx.run("./convert_gherkin-languages.py")
+- ctx.run("diff i18n.py ../../behave/i18n.py")
+- if not dry_run:
++
++ # -- DIFF: Returns normally w/ non-zero exitcode => NEEDS: warn=True
++ languages_have_changed = False
++ result = ctx.run("diff i18n.py ../../behave/i18n.py", warn=True, hide=True)
++ languages_have_changed = not result.ok
++ if verbose and languages_have_changed:
++ # -- SHOW DIFF:
++ print(result.stdout)
++
++ if not languages_have_changed:
++ print("NO_CHANGED: gherkin-languages.json")
++ elif not dry_run:
+ print("Updating behave/i18n.py ...")
+ Path("i18n.py").move("../../behave/i18n.py")
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
new file mode 100644
index 000000000..4381005a8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
@@ -0,0 +1,22 @@
+From f9722bff21248b9a428953ee4e59334d0c0e78b6 Mon Sep 17 00:00:00 2001
+From: santosh653 <70637961+santosh653@users.noreply.github.com>
+Date: Mon, 14 Dec 2020 12:05:50 -0500
+Subject: [PATCH] Test against PowerPC CPU support (Travis) (#867)
+
+Run test suite against both AMD and PowerPC architecture
+---
+ .travis.yml | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/.travis.yml b/.travis.yml
+index 781a610..2b78d97 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,3 +1,7 @@
++arch:
++ - amd64
++ - ppc64le
++
+ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
diff --git a/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
new file mode 100644
index 000000000..c1f099909
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
@@ -0,0 +1,132 @@
+From 1ee8e0da2cc52303c65f32889e603d930b01bda9 Mon Sep 17 00:00:00 2001
+From: Korijn van Golen <k.vangolen@clinicalgraphics.com>
+Date: Sat, 28 Nov 2020 11:39:28 +0100
+Subject: [PATCH] Add Context.attach, docs and test
+
+---
+ behave/formatter/json.py | 6 +++--
+ behave/runner.py | 11 +++++++++
+ docs/formatters.rst | 18 ++++++++++++++
+ features/formatter.json.feature | 42 +++++++++++++++++++++++++++++++++
+ 4 files changed, 75 insertions(+), 2 deletions(-)
+
+diff --git a/behave/formatter/json.py b/behave/formatter/json.py
+index 6da0d59..edfe3d7 100644
+--- a/behave/formatter/json.py
++++ b/behave/formatter/json.py
+@@ -168,10 +168,12 @@ class JSONFormatter(Formatter):
+ self._step_index += 1
+
+ def embedding(self, mime_type, data):
+- step = self.current_feature_element["steps"][-1]
++ step = self.current_feature_element["steps"][self._step_index]
++ if "embeddings" not in step:
++ step["embeddings"] = []
+ step["embeddings"].append({
+ "mime_type": mime_type,
+- "data": base64.b64encode(data).replace("\n", ""),
++ "data": base64.b64encode(data).decode(self.stream.encoding or "utf-8"),
+ })
+
+ def eof(self):
+diff --git a/behave/runner.py b/behave/runner.py
+index d01bff0..c583caf 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -475,6 +475,17 @@ class Context(object):
+ # -- AVOID DUPLICATES:
+ current_frame["@cleanups"].append(internal_cleanup_func)
+
++ def attach(self, mime_type, data):
++ """Embeds data (e.g. a screenshot) in reports for all
++ formatters that support it, such as the JSON formatter.
++
++ :param mime_type: MIME type of the binary data.
++ :param data: Bytes-like object to embed.
++ """
++ is_compatible = lambda f: hasattr(f, "embedding")
++ for formatter in filter(is_compatible, self._runner.formatters):
++ formatter.embedding(mime_type, data)
++
+
+ @contextlib.contextmanager
+ def use_context_with_mode(context, mode):
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index a40fd8d..534468a 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -116,3 +116,21 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ [behave.formatters]
+ allure = allure_behave.formatter:AllureFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
++
++
++Embedding data (e.g. screenshots) in reports
++------------------------------------------------------------------------------
++
++You can embed data in reports with the :class:`~behave.runner.Context` method
++:func:`~behave.runner.Context.attach`, if you have configured a formatter that
++supports it. Currently only the JSON formatter supports embedding data.
++
++For example:
++
++.. code-block:: python
++
++ @when(u'I open the Google webpage')
++ def step_impl(context):
++ context.browser.get('http://www.google.com')
++ img = context.browser.get_full_page_screenshot_as_png()
++ context.attach("image/png", img)
+diff --git a/features/formatter.json.feature b/features/formatter.json.feature
+index 96b28c7..67c97ae 100644
+--- a/features/formatter.json.feature
++++ b/features/formatter.json.feature
+@@ -309,6 +309,48 @@ Feature: JSON Formatter
+ But note that "both matched arguments.values are provided as string"
+
+
++ Scenario: Use JSON formatter and embed binary data in report from two steps
++ Given a file named "features/json_embeddings.feature" with:
++ """
++ Feature:
++ Scenario: Use embeddings
++ Given "foobar" as plain text
++ And "red" as plain text
++ """
++ And a file named "features/steps/json_embeddings_steps.py" with:
++ """
++ from behave import step
++
++ @step('"{data}" as plain text')
++ def step_string(context, data):
++ context.attach("text/plain", data.encode("utf-8"))
++ """
++ When I run "behave -f json.pretty features/json_embeddings.feature"
++ Then it should pass with:
++ """
++ 1 feature passed, 0 failed, 0 skipped
++ 1 scenario passed, 0 failed, 0 skipped
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "Zm9vYmFy",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++ And the command output should contain:
++ """
++ "embeddings": [
++ {
++ "data": "cmVk",
++ "mime_type": "text/plain"
++ }
++ ],
++ """
++
++
+ @xfail
+ @regression_problem.with_duration
+ Scenario: Use JSON formatter with feature and one scenario with steps
diff --git a/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
new file mode 100644
index 000000000..766f9f29f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
@@ -0,0 +1,125 @@
+From 186660dd8d463122ddf364d6207d7f62645a7404 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:45:26 +0100
+Subject: [PATCH] Add Contributing chapter
+
+---
+ docs/conf.py | 2 +-
+ docs/contributing.rst | 82 +++++++++++++++++++++++++++++++++++++++++++
+ docs/index.rst | 1 +
+ 3 files changed, 84 insertions(+), 1 deletion(-)
+ create mode 100644 docs/contributing.rst
+
+diff --git a/docs/conf.py b/docs/conf.py
+index e55fb21..1579a36 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2019, %s" % authors
++copyright = u"2012-2020, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+new file mode 100644
+index 0000000..78f36bd
+--- /dev/null
++++ b/docs/contributing.rst
+@@ -0,0 +1,82 @@
++Contributing
++============
++
++If you find a bug you can fix or want to contribute an enhancement you're
++welcome to `open an issue`_ on GitHub or create a `pull request`_ directly.
++
++.. _open an issue: https://github.com/behave/behave/issues
++.. _pull request: https://github.com/behave/behave/pulls
++
++Using ``invoke`` for Development
++--------------------------------
++
++For most development tasks we have `invoke`_ commands.
++
++Install all requirements for running tasks using Pip, e.g.
++
++.. code-block:: console
++
++ python3 -m pip install -r tasks/py.requirements.txt
++
++Display all available ``invoke`` commands like this:
++
++.. code-block:: console
++
++ invoke -l
++
++If you're curious, all ``invoke`` tasks are located in the ``tasks/``
++folder.
++
++.. _invoke: https://www.pyinvoke.org/
++
++Update Gherkin Language Specification
++-------------------------------------
++
++An ``invoke`` command will download the latest Gherkin language
++specification and update the `behave/i18n.py`_ module:
++
++.. code-block:: console
++
++ invoke develop.update-gherkin
++
++If there were changes this command will have updated two files:
++
++#. ``etc/gherkin/gherkin-languages.json`` (original Cucumber JSON spec)
++#. ``behave/i18n.py`` (Python module generated from the JSON spec)
++
++Put both under version control and open a PR to merge them.
++
++.. _behave/i18n.py:
++ https://github.com/behave/behave/blob/master/behave/i18n.py
++
++Update Documentation
++--------------------
++
++Our documentation is written in `reStructuredText`_, and built and hosted
++on `ReadTheDocs`_. Make your changes to the files in the ``docs/`` folder
++and build the documentation with:
++
++.. code-block:: console
++
++ invoke docs
++
++or, alternatively, using Tox:
++
++.. code-block:: console
++
++ tox -e docs
++
++.. hint::
++
++ Building the docs requires Sphinx and DocUtils. If your build fails
++ because those are missing, run:
++
++ python3 -m pip install -r py.requirements/docs.txt
++
++Once the docs are built successfully, ``sphinx`` will tell you where it
++generated the HTML output (typically ``build/docs/html``), which you can
++then inspect locally.
++
++.. _reStructuredText:
++ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
++.. _ReadTheDocs: https://readthedocs.org/
+diff --git a/docs/index.rst b/docs/index.rst
+index f0dd20e..e00079c 100644
+--- a/docs/index.rst
++++ b/docs/index.rst
+@@ -43,6 +43,7 @@ Contents
+ comparison
+ new_and_noteworthy
+ more_info
++ contributing
+ appendix
+
+ .. seealso::
diff --git a/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
new file mode 100644
index 000000000..9fb7c2660
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
@@ -0,0 +1,34 @@
+From fb9dab72e6494ffe4c7214c05cfb8931318bdf99 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Sat, 12 Dec 2020 19:46:29 +0100
+Subject: [PATCH] Adapt Tox target for building the docs
+
+This will generate the HTML docs in the same location as `invoke docs`.
+---
+ tox.ini | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/tox.ini b/tox.ini
+index b825921..8ccb58b 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -77,12 +77,9 @@ setenv =
+
+
+ [testenv:docs]
+-basepython= python2
+ changedir = docs
+-commands=
+- sphinx-build -W -b html -D language=en -d {envtmpdir}/doctrees . {envtmpdir}/html/en
+-deps=
+- -r{toxinidir}/py.requirements/docs.txt
++commands = sphinx-build -W -b html -D language=en -d {toxinidir}/build/docs/doctrees . {toxinidir}/build/docs/html/en
++deps = -r{toxinidir}/py.requirements/docs.txt
+
+
+ [testenv:cleanroom2]
+@@ -146,4 +143,3 @@ commands=
+ deps=
+ jit
+ {[testenv]deps}
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
new file mode 100644
index 000000000..402dedcc8
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
@@ -0,0 +1,83 @@
+From 380036aba8f54d3b710e6b635ca0367a5fe94607 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 11:30:17 +0100
+Subject: [PATCH] Mention HTML formatter in documentation
+
+---
+ docs/formatters.rst | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/docs/formatters.rst b/docs/formatters.rst
+index 534468a..6080fc4 100644
+--- a/docs/formatters.rst
++++ b/docs/formatters.rst
+@@ -1,8 +1,8 @@
+ .. _id.appendix.formatters:
+
+-==============================================================================
++========================
+ Formatters and Reporters
+-==============================================================================
++========================
+
+ :pypi:`behave` provides 2 different concepts for reporting results of a test run:
+
+@@ -15,7 +15,7 @@ The ``Reporter`` has a more coarse-grained API.
+
+
+ Reporters
+-------------------------------------------------------------------------------
++---------
+
+ The following reporters are currently supported:
+
+@@ -28,7 +28,7 @@ summary Provides a summary of the test run.
+
+
+ Formatters
+-------------------------------------------------------------------------------
++----------
+
+ The following formatters are currently supported:
+
+@@ -62,7 +62,7 @@ tags.location dry-run Shows tags and the location where they are used.
+
+
+ User-Defined Formatters
+-------------------------------------------------------------------------------
++-----------------------
+
+ Behave allows you to provide your own formatter (class)::
+
+@@ -96,16 +96,16 @@ to provide them. The formatter should use the attribute schema:
+
+
+ More Formatters
+-------------------------------------------------------------------------------
++---------------
+
+-The following formatters are currently known:
++The following contributed formatters are currently known:
+
+ ============== =========================================================================
+ Name Description
+ ============== =========================================================================
+-allure :pypi:`allure-behave`, an Allure formatter for behave:
+- ``allure_behave.formatter:AllureFormatter``
+-teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns
++allure :pypi:`allure-behave`, an Allure formatter for behave.
++html :pypi:`behave-html-formatter`, a simple HTML formatter for behave.
++teamcity :pypi:`behave-teamcity`, a formatter for JetBrains TeamCity CI testruns
+ with behave.
+ ============== =========================================================================
+
+@@ -114,7 +114,8 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te
+ # -- FILE: behave.ini
+ # FORMATTER ALIASES: behave -f allure ...
+ [behave.formatters]
+- allure = allure_behave.formatter:AllureFormatter
++ allure = allure_behave.formatter:AllureFormatter
++ html = behave_html_formatter:HTMLFormatter
+ teamcity = behave_teamcity:TeamcityFormatter
+
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
new file mode 100644
index 000000000..cfc17049e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
@@ -0,0 +1,22 @@
+From a078b93a1e1e09f5ce66ee1de656a09dc01456f7 Mon Sep 17 00:00:00 2001
+From: Peter Bittner <django@bittner.it>
+Date: Tue, 15 Dec 2020 12:44:52 +0100
+Subject: [PATCH] Use console highlighting for `pip install` (docs)
+
+---
+ docs/contributing.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/docs/contributing.rst b/docs/contributing.rst
+index 78f36bd..f3deb87 100644
+--- a/docs/contributing.rst
++++ b/docs/contributing.rst
+@@ -71,6 +71,8 @@ or, alternatively, using Tox:
+ Building the docs requires Sphinx and DocUtils. If your build fails
+ because those are missing, run:
+
++ .. code-block:: console
++
+ python3 -m pip install -r py.requirements/docs.txt
+
+ Once the docs are built successfully, ``sphinx`` will tell you where it
diff --git a/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
new file mode 100644
index 000000000..751b742c1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
@@ -0,0 +1,52 @@
+From 185609edb6df9cc8fd1896abb4d9546aa568f67f Mon Sep 17 00:00:00 2001
+From: Tim Gates <tim.gates@iress.com>
+Date: Sun, 27 Dec 2020 08:16:16 +1100
+Subject: [PATCH] docs: fix simple typo, tuorial -> tutorial
+
+There is a small typo in docs/more_info.rst.
+
+Should read `tutorial` rather than `tuorial`.
+---
+ docs/more_info.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/docs/more_info.rst b/docs/more_info.rst
+index d0b9fcd..0d87a9c 100644
+--- a/docs/more_info.rst
++++ b/docs/more_info.rst
+@@ -66,7 +66,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ 2012-08-12, PyCon Australia.
+
+-* Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++* Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -84,7 +84,7 @@ Presentation Videos
+ * Benno Rice: `Making Your Application Behave`_ (30min),
+ PyCon Australia, 2012-08-12
+
+- * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28,
++ * Selenium: `First behave python tutorial with selenium`_ (8min), 2015-01-28,
+ http://www.seleniumframework.com/python-basic/first-behave-gherkin/
+
+ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16
+@@ -112,7 +112,7 @@ Presentation Videos
+ :width: 600
+ :height: 400
+
+- Selenium: `First behave python tuorial with selenium`_
++ Selenium: `First behave python tutorial with selenium`_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :Date: 2015-01-28
+@@ -146,7 +146,7 @@ Presentation Videos
+
+
+ .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg
+-.. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
++.. _`First behave python tutorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk
+ .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ
+ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
new file mode 100644
index 000000000..715d9a41b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
@@ -0,0 +1,227 @@
+From 3c55eea13ed7b547ce548518bec7aeb96f4d2662 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 21:52:25 +0200
+Subject: [PATCH] Add support for python3.9 (by using active-tags).
+
+---
+ features/step.async_steps.feature | 21 +++++++++++++++++++++
+ issue.features/issue0330.feature | 6 ++++++
+ issue.features/issue0446.feature | 4 ++++
+ issue.features/issue0457.feature | 5 +++++
+ issue.features/issue0657.feature | 3 +++
+ 5 files changed, 39 insertions(+)
+
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 3a18fa9..06709d9 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -32,6 +32,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+@@ -63,6 +66,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-step with @async_run_until_complete (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+@@ -93,6 +99,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+@@ -128,6 +137,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
+@@ -170,6 +182,9 @@ Feature: Async-Test Support (async-step, ...)
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step raises error
+ Given a new working directory
+@@ -213,6 +228,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+@@ -250,6 +268,9 @@ Feature: Async-Test Support (async-step, ...)
+ @use.with_python.version=3.4
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use async-dispatch and async-collect concepts
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index 81cb6e2..be4d378 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -71,6 +71,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -85,6 +86,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped feature is created with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+@@ -101,6 +103,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -121,6 +124,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+ And note that "Charly2 is the skipped scenarion in charly.feature"
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+@@ -144,6 +148,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+@@ -165,6 +170,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 901bdec..12de37a 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -59,6 +59,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+ """
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -88,6 +89,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in before_scenario()
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+@@ -121,6 +123,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+@@ -152,6 +155,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Hook error in after_scenario()
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 46f96e9..6d2f48f 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -25,6 +25,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -46,6 +47,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ """
+
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use failing assertation in a JUnit XML report
+ Given a file named "features/fails1.feature" with:
+ """
+@@ -70,6 +72,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+
+ @not.with_python.version=3.8
++ @not.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+@@ -90,7 +93,9 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+ <error message="My name is "Bob" and <here> I am"
+ """
+
++
+ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ Scenario: Use exception in a JUnit XML report
+ Given a file named "features/fails2.feature" with:
+ """
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index f667893..aeaefd2 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -5,6 +5,9 @@ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise e
+
+ @use.with_python.version=3.5
+ @use.with_python.version=3.6
++ @use.with_python.version=3.7
++ @use.with_python.version=3.8
++ @use.with_python.version=3.9
+ @async_step_fails
+ Scenario: Use @async_run_until_complete and async-step fails
+ Given a new working directory
diff --git a/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
new file mode 100644
index 000000000..f8760e991
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
@@ -0,0 +1,19 @@
+From e7de712e7507075c4e7bd9f9fe9ff812641887e9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 12 Oct 2020 22:14:59 +0200
+Subject: [PATCH] PREFER: python3 from now on.
+
+---
+ bin/behave | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bin/behave b/bin/behave
+index c02e8d3..9ebb584 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
+ from __future__ import absolute_import
diff --git a/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
new file mode 100644
index 000000000..294255e19
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
@@ -0,0 +1,41 @@
+From 48858b58166b7d49b3f6280781df58b3874ac456 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:04 +0100
+Subject: [PATCH] REMOVE: invoke scripts
+
+---
+ bin/invoke | 8 --------
+ bin/invoke.cmd | 9 ---------
+ 2 files changed, 17 deletions(-)
+ delete mode 100755 bin/invoke
+ delete mode 100644 bin/invoke.cmd
+
+diff --git a/bin/invoke b/bin/invoke
+deleted file mode 100755
+index e9800e8..0000000
+--- a/bin/invoke
++++ /dev/null
+@@ -1,8 +0,0 @@
+-#!/bin/sh
+-#!/bin/bash
+-# RUN INVOKE: From bundled ZIP file.
+-
+-HERE=$(dirname $0)
+-export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-
+-python ${HERE}/../tasks/_vendor/invoke.zip $*
+diff --git a/bin/invoke.cmd b/bin/invoke.cmd
+deleted file mode 100644
+index 9303432..0000000
+--- a/bin/invoke.cmd
++++ /dev/null
+@@ -1,9 +0,0 @@
+-@echo off
+-REM RUN INVOKE: From bundled ZIP file.
+-
+-setlocal
+-set HERE=%~dp0
+-set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
+-if not defined PYTHON set PYTHON=python
+-
+-%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
new file mode 100644
index 000000000..740c305b4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
@@ -0,0 +1,124 @@
+From 204d6e0eab7f830aaff7dacfa457a08d4b3e44b7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:52:42 +0100
+Subject: [PATCH] FIX: Deprecated warnings for Python 3.x
+
+---
+ bin/json.format.py | 15 ++++++++++-----
+ bin/jsonschema_validate.py | 23 ++++++++++++++++-------
+ 2 files changed, 26 insertions(+), 12 deletions(-)
+
+diff --git a/bin/json.format.py b/bin/json.format.py
+index b7eb05f..b75d88a 100755
+--- a/bin/json.format.py
++++ b/bin/json.format.py
+@@ -10,8 +10,8 @@ LICENSE: BSD
+ from __future__ import absolute_import
+
+ __author__ = "Jens Engel"
+-__copyright__ = "(c) 2011-2013 by Jens Engel"
+-VERSION = "0.2.2"
++__copyright__ = "(c) 2011-2021 by Jens Engel"
++VERSION = "0.3.0"
+
+ # -- IMPORTS:
+ import os.path
+@@ -29,6 +29,7 @@ except ImportError:
+ # CONSTANTS:
+ # ----------------------------------------------------------------------------
+ DEFAULT_INDENT_SIZE = 2
++PYTHON_VERSION = sys.version_info[:2]
+
+ # ----------------------------------------------------------------------------
+ # FUNCTIONS:
+@@ -58,7 +59,11 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ # return 0
+
+ contents = open(filename, "r").read()
+- data = json.loads(contents, encoding=encoding)
++ if PYTHON_VERSION >= (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ data = json.loads(contents)
++ else:
++ data = json.loads(contents, encoding=encoding)
+ contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
+ contents2 = contents2.strip()
+ contents2 = "%s\n" % contents2
+@@ -69,7 +74,7 @@ def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
+ outfile = open(filename, "w")
+ outfile.write(contents2)
+ outfile.close()
+- console.warn("%s OK", message)
++ console.warning("%s OK", message)
+ return 1 #< OK
+
+ def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
+@@ -143,7 +148,7 @@ Format/Beautify one or more JSON file(s)."""
+ console.info("SKIP %s, no JSON files found in dir.", filename)
+ skipped += 1
+ elif not os.path.exists(filename):
+- console.warn("SKIP %s, file not found.", filename)
++ console.warning("SKIP %s, file not found.", filename)
+ skipped += 1
+ continue
+ else:
+diff --git a/bin/jsonschema_validate.py b/bin/jsonschema_validate.py
+index db2edb1..fe7596e 100755
+--- a/bin/jsonschema_validate.py
++++ b/bin/jsonschema_validate.py
+@@ -18,11 +18,11 @@ from __future__ import absolute_import, print_function
+ __author__ = "Jens Engel"
+ __version__ = "0.1.0"
+
+-from jsonschema import validate
+ import argparse
+ import os.path
+ import sys
+ import textwrap
++from jsonschema import validate
+ try:
+ import json
+ except ImportError:
+@@ -38,16 +38,28 @@ except ImportError:
+ HERE = os.path.dirname(__file__)
+ TOP = os.path.normpath(os.path.join(HERE, ".."))
+ SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema")
++PYTHON_VERSION = sys.version_info[:2]
+
+
+ # -----------------------------------------------------------------------------
+ # FUNCTIONS:
+ # -----------------------------------------------------------------------------
+-def jsonschema_validate(filename, schema, encoding=None):
++def json_loads(text, encoding=None):
++ kwargs = {}
++ if encoding and PYTHON_VERSION < (3, 1):
++ # -- NOTE: encoding keyword is deprecated since python 3.1
++ kwargs["encoding"] = encoding
++ return json.loads(text, **kwargs)
++
++def json_load(filename, encoding=None):
+ f = open(filename, "r")
+ contents = f.read()
+ f.close()
+- data = json.loads(contents, encoding=encoding)
++ data = json_loads(contents, encoding=encoding)
++ return data
++
++def jsonschema_validate(filename, schema, encoding=None):
++ data = json_load(filename, encoding=encoding)
+ return validate(data, schema)
+
+
+@@ -89,10 +101,7 @@ def main(args=None):
+ parser.error("SCHEMA not found: %s" % options.schema)
+
+ try:
+- f = open(options.schema, "r")
+- contents = f.read()
+- f.close()
+- schema = json.loads(contents, encoding=options.encoding)
++ schema = json_load(options.schema, encoding=options.encoding)
+ except Exception as e:
+ msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e)
+ sys.exit(msg)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
new file mode 100644
index 000000000..6b54b37b9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
@@ -0,0 +1,18 @@
+From 2a0f16e22b3e7ed46d106b25eb1b3c186c84762d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 21:53:32 +0100
+Subject: [PATCH] FIX: Python2 problems in virtualenvs w/ behave4cmd0 steps.
+
+---
+ bin/behave | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bin/behave b/bin/behave
+index 9ebb584..0f37dec 100755
+--- a/bin/behave
++++ b/bin/behave
+@@ -1,3 +1,4 @@
++#!/usr/bin/env python
+ #!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
new file mode 100644
index 000000000..2a4c71092
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
@@ -0,0 +1,875 @@
+From 528b1a27fe38da4186c8548a3249e0c5b04cc8e8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:04:18 +0100
+Subject: [PATCH] FIX: Active-tag logic
+
+Fix active-tag computation logic if multiple active-tags exist
+that use the same category. It is now possible to use
+positive (use.with_xxx) and negative (not.with_xxx) tags
+on the same model element (Feature, Scenario, ...).
+---
+ behave/api/runtime_constraint.py | 12 +-
+ behave/tag_matcher.py | 82 ++++-
+ features/tags.active_tags.feature | 22 +-
+ tests/functional/test_active_tags.py | 529 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 45 +--
+ 5 files changed, 624 insertions(+), 66 deletions(-)
+ create mode 100644 tests/functional/test_active_tags.py
+
+diff --git a/behave/api/runtime_constraint.py b/behave/api/runtime_constraint.py
+index 310e529..e5a36a0 100644
+--- a/behave/api/runtime_constraint.py
++++ b/behave/api/runtime_constraint.py
+@@ -7,6 +7,8 @@ Simplifies to specify runtime constraints in
+ """
+
+ from __future__ import absolute_import
++import six
++import sys
+ from behave.exception import ConstraintError
+
+
+@@ -19,11 +21,10 @@ def require_min_python_version(minimal_version):
+ :param minimal_version: Minimum version (as string, tuple)
+ :raises: behave.exception.ConstraintError
+ """
+- import six
+- import sys
+ python_version = sys.version_info
+ if isinstance(minimal_version, six.string_types):
+- python_version = "%s.%s" % sys.version_info[:2]
++ python_version = float("%s.%s" % sys.version_info[:2])
++ minimal_version = float(minimal_version)
+ elif not isinstance(minimal_version, tuple):
+ raise TypeError("string or tuple (was: %s)" % type(minimal_version))
+
+@@ -40,6 +41,9 @@ def require_min_behave_version(minimal_version):
+ """
+ # -- SIMPLISTIC IMPLEMENTATION:
+ from behave.version import VERSION as behave_version
+- if behave_version < minimal_version:
++ behave_version2 = behave_version.split(".")
++ minimal_version2 = minimal_version.split(".")
++ if behave_version2 < minimal_version2:
++ # -- USE: Tuple comparison as version comparison.
+ raise ConstraintError("behave >= %s expected (was: %s)" % \
+ (minimal_version, behave_version))
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index 5f9dce0..e2b1e82 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ """
+-Contains classes and functionality to provide a skip-if logic based on tags
+-in feature files.
++Contains classes and functionality to provide the active-tag mechanism.
++Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+ from __future__ import absolute_import
+@@ -10,6 +10,11 @@ import operator
+ import six
+
+
++def bool_to_string(value):
++ """Converts a Boolean value into its normalized string representation."""
++ return str(bool(value)).lower()
++
++
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -36,12 +41,13 @@ class TagMatcher(object):
+ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+- TAG SCHEMA:
++ TAG SCHEMA 1 (preferred):
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++
++ TAG SCHEMA 2:
+ * active.with_{category}={value}
+ * not_active.with_{category}={value}
+- * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+ ----------
+@@ -52,7 +58,7 @@ class ActiveTagMatcher(TagMatcher):
+ active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ...
+ active_tags.enabled := enabled(group1) and enabled(group2) and ...
+
+- All active-tag groups must be turned "on".
++ All active-tag groups must be turned "on" (enabled).
+ Otherwise, the model element should be excluded.
+
+ CONCEPT: ValueProvider
+@@ -81,12 +87,12 @@ class ActiveTagMatcher(TagMatcher):
+ # -- FILE: features/alice.feature
+ Feature:
+
+- @active.with_os=win32
++ @use.with_os=win32
+ Scenario: Alice (Run only on Windows)
+ Given I do something
+ ...
+
+- @not_active.with_browser=chrome
++ @not.with_browser=chrome
+ Scenario: Bob (Excluded with Web-Browser Chrome)
+ Given I do something else
+ ...
+@@ -116,7 +122,7 @@ class ActiveTagMatcher(TagMatcher):
+ scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set.
+ """
+ value_separator = "="
+- tag_prefixes = ["active", "not_active", "use", "not", "only"]
++ tag_prefixes = ["use", "not", "active", "not_active", "only"]
+ tag_schema = r"^(?P<prefix>%s)\.with_(?P<category>\w+(\.\w+)*)%s(?P<value>.*)$"
+ ignore_unknown_categories = True
+ use_exclude_reason = False
+@@ -163,21 +169,49 @@ class ActiveTagMatcher(TagMatcher):
+
+ def is_tag_group_enabled(self, group_category, group_tag_pairs):
+ """Provides boolean logic to determine if all active-tags
+- which use the same category result in a enabled value.
+-
+- Use LOGICAL-OR expression for active-tags with same category::
+-
+- category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ...
++ which use the same category result in an enabled value.
+
+ .. code-block:: gherkin
+
+ @use.with_xxx=alice
+ @use.with_xxx=bob
+ @not.with_xxx=charly
++ @not.with_xxx=doro
+ Scenario:
+ Given a step passes
+ ...
+
++ Use LOGICAL expression for active-tags with same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++ xxx | Only use parts: (xxx == "alice") or (xxx == "bob")
++ -------+-------------------
++ alice | true
++ bob | true
++ other | false
++
++ xxx | Only not parts:
++ | (not xxx == "charly") and (not xxx == "doro")
++ | = not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ charly | false
++ doro | false
++ other | true
++
++ xxx | Use and not parts:
++ | ((xxx == "alice") or (xxx == "bob")) and not((xxx == "charly") or (xxx == "doro"))
++ -------+-------------------
++ alice | true
++ bob | true
++ charly | false
++ doro | false
++ other | false
++
+ :param group_category: Category for this tag-group (as string).
+ :param category_tag_group: List of active-tag match-pairs.
+ :return: True, if tag-group is enabled.
+@@ -191,20 +225,28 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+- tags_enabled = []
++ positive_tags_matched = []
++ negative_tags_matched = []
+ for category_tag, tag_match in group_tag_pairs:
+ tag_prefix = tag_match.group("prefix")
+ category = tag_match.group("category")
+ tag_value = tag_match.group("value")
+ assert category == group_category
+
+- is_category_tag_switched_on = operator.eq # equal_to
+ if self.is_tag_negated(tag_prefix):
+- is_category_tag_switched_on = operator.ne # not_equal_to
+-
+- tag_enabled = is_category_tag_switched_on(tag_value, current_value)
+- tags_enabled.append(tag_enabled)
+- return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression
++ # -- CASE: @not.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ negative_tags_matched.append(tag_matched)
++ else:
++ # -- CASE: @use.with_CATEGORY=VALUE
++ tag_matched = (tag_value == current_value)
++ positive_tags_matched.append(tag_matched)
++ tag_expression1 = any(positive_tags_matched) #< LOGICAL-OR expression
++ tag_expression2 = any(negative_tags_matched) #< LOGICAL-OR expression
++ if not positive_tags_matched:
++ tag_expression1 = True
++ tag_group_enabled = bool(tag_expression1 and not tag_expression2)
++ return tag_group_enabled
+
+ def should_exclude_with(self, tags):
+ group_categories = self.group_active_tags_by_category(tags)
+diff --git a/features/tags.active_tags.feature b/features/tags.active_tags.feature
+index 4ab55c2..6adcb60 100644
+--- a/features/tags.active_tags.feature
++++ b/features/tags.active_tags.feature
+@@ -240,9 +240,25 @@ Feature: Active Tags
+ | tags | enabled? | Comment |
+ | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 |
+ | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2|
+- | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) |
+- | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none |
+- | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 |
++ | @use.with_foo=other @not.with_foo=xxx | no | Disabled: none |
++ | @not.with_foo=other @not.with_foo=xxx | no | Disabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=xxx | no | Disabled: tag1 (BAD-SPEC, CONFLICTS) |
++
++
++ Scenario: Tag logic with three active tags of same category
++ Given I setup the current values for active tags with:
++ | category | value |
++ | foo | xxx |
++ Then the following active tag combinations are enabled:
++ | tags | enabled? | Comment |
++ | @use.with_foo=xxx @use.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @use.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @use.with_foo=other2| yes | Enabled: tag1 |
++ | @use.with_foo=xxx @not.with_foo=other @not.with_foo=other2| yes | Enabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @use.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @use.with_foo=other2| no | Disabled: tag1 |
++ | @not.with_foo=xxx @not.with_foo=other @not.with_foo=other2| no | Disabled: tag1 |
+
+
+ Scenario: Tag logic with unknown categories (case: ignored)
+diff --git a/tests/functional/test_active_tags.py b/tests/functional/test_active_tags.py
+new file mode 100644
+index 0000000..fd6138f
+--- /dev/null
++++ b/tests/functional/test_active_tags.py
+@@ -0,0 +1,529 @@
++# -*- coding: utf-8 -*-
++"""
++Functionals tests for active tag-matcher (mod:`behave.tag_matcher`).
++"""
++
++from __future__ import absolute_import, print_function
++import pytest
++from behave.tag_matcher import ActiveTagMatcher
++
++# =============================================================================
++# TEST DATA:
++# =============================================================================
++# VALUE_PROVIDER = {
++# "foo": "alice",
++# "bar": "BOB",
++# }
++
++# =============================================================================
++# PYTEST FIXTURES:
++# =============================================================================
++# @pytest.fixture
++# def active_tag_matcher():
++# tag_matcher = ActiveTagMatcher(VALUE_PROVIDER)
++# return tag_matcher
++
++
++# =============================================================================
++# TEST SUITE:
++# =============================================================================
++class TestActivateTags(object):
++ VALUE_PROVIDER = {
++ "foo": "Frank",
++ "bar": "Bob",
++ # "OTHER": "VALUE",
++ }
++
++ def check_should_run_with_active_tags(self, case, expected, tags):
++ # -- tag_matcher.should_run_with(tags).result := expected
++ case += " (tags: {tags})"
++ tag_matcher = ActiveTagMatcher(self.VALUE_PROVIDER)
++ actual_result1 = tag_matcher.should_run_with(tags)
++ actual_result2 = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result1, case.format(tags=tags)
++ assert (not expected) == actual_result2
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_foo=VALUE matches", True, ["use.with_foo=Frank"]),
++ ("use.with_foo=VALUE mismatches", False, ["use.with_foo=OTHER"]),
++ ("not.with_foo=VALUE matches", False, ["not.with_foo=Frank"]),
++ ("not.with_foo=VALUE mismatches", True, ["not.with_foo=OTHER"]),
++ ("NO_TAGS", True, []),
++ ])
++ def test_one_tag_for_category1(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("use.with_bar=Bob matches", True, ["use.with_bar=Bob"]),
++ ("use.with_bar=VALUE mismatches", False, ["use.with_bar=OTHER"]),
++ ("not.with_bar=VALUE matches", False, ["not.with_bar=Bob"]),
++ ("not.with_bar=VALUE mismatches", True, ["not.with_bar=OTHER"]),
++ ])
++ def test_one_tag_for_category2(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ # @pytest.mark.parametrize("case, expected, tags", [
++ # ("use.with_OTHER=VALUE matches", True, ["use.with_OTHER=VALUE"]),
++ # ("use.with_OTHER=VALUE mismatches", False, ["use.with_OTHER=OTHER"]),
++ # ("not.with_OTHER=VALUE matches", False, ["not.with_OTHER=VALUE"]),
++ # ("not.with_OTHER=VALUE mismatches", True, ["not.with_OTHER=OTHER"]),
++ # ])
++ # def test_one_tag_for_other_category(self, case, expected, tags):
++ # self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("2x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER"]),
++ ("2x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER"]),
++ ("1x use./not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER"]),
++ ])
++ def test_one_category_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ ("3x use.with_foo=VALUE: one matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("3x not.with_foo=VALUE: one matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "use.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("2x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "not.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ("1x use.with_foo=VALUE: use-matches", True,
++ ["use.with_foo=Frank", "not.with_foo=OTHER_1", "not.with_foo=OTHER_2"]),
++ ("1x not.with_foo=VALUE: not-matches", False,
++ ["not.with_foo=Frank", "use.with_foo=OTHER_1", "use.with_foo=OTHER_2"]),
++ ])
++ def test_one_category_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_two_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- use.with_CATEGORY=VALUE
++ ("use.with_... 2x matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=Frank", "use.with_bar=OTHER"]),
++ ("use.with_... 1x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_bar=OTHER2", "use.with_bar=OTHER"]),
++ # -- not.with_CATEGORY=VALUE
++ ("not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("not.with_... 1x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("not.with_... 0x matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- use.with_CATEGORY_1=VALUE_1, not.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... use-matches", True, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=OTHER"]),
++ ("use./not.with_... not-matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["use.with_foo=Frank", "use.with_foo=OTHER", "not.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["use.with_foo=OTHER", "use.with_foo=OTHER2", "not.with_bar=OTHER"]),
++ # -- not.with_CATEGORY_1=VALUE_1, use.with_CATEGORY_2=VALUE_2
++ ("use./not.with_... not-matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=OTHER"]),
++ ("use./not.with_... use-matches", True, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=Bob"]),
++ ("use./not.with_... 2x matches", False, ["not.with_foo=Frank", "not.with_foo=OTHER", "use.with_bar=Bob"]),
++ ("use./not.with_... 0x matches", False, ["not.with_foo=OTHER", "not.with_foo=OTHER2", "use.with_bar=OTHER"]),
++ ])
++ def test_two_categories_with_three_tags(self, case, expected, tags):
++ self.check_should_run_with_active_tags(case, expected, tags)
++
++'''
++class Traits4ActiveTagMatcher(object):
++ TagMatcher = ActiveTagMatcher
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++
++ category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice")
++ category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob")
++ category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly")
++ category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2")
++ category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB")
++ category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY")
++ category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2")
++ unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one")
++
++ # -- NEGATED TAGS:
++ category1_not_enabled_tag = \
++ TagMatcher.make_category_tag("foo", "alice", "not_active")
++ category1_not_enabled_tag2 = \
++ TagMatcher.make_category_tag("foo", "alice", "not")
++ category1_not_disabled_tag = \
++ TagMatcher.make_category_tag("foo", "bob", "not_active")
++ category1_negated_similar_tag1 = \
++ TagMatcher.make_category_tag("foo", "alice2", "not_active")
++
++
++ active_tags1 = [
++ category1_enabled_tag, category1_disabled_tag, category1_similar_tag,
++ category1_not_enabled_tag, category1_not_enabled_tag2,
++ ]
++ active_tags2 = [
++ category2_enabled_tag, category2_disabled_tag, category2_similar_tag,
++ ]
++ active_tags = active_tags1 + active_tags2
++
++
++# -- REQUIRES: pytest
++class TestActiveTagMatcher2(object):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ tag_matcher = cls.TagMatcher(value_provider)
++ return tag_matcher
++
++ @pytest.mark.parametrize("case, expected_len, tags", [
++ ("case: Two enabled tags", 2,
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag", 1,
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag", 1,
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Normal and active negated tag", 1,
++ ["foo", traits.category1_not_enabled_tag]),
++ ("case: Two normal tags", 0,
++ ["foo", "bar"]),
++ ])
++ def test_select_active_tags__with_two_tags(self, case, expected_len, tags):
++ tag_matcher = self.make_tag_matcher()
++ selected = tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ assert len(selected) == expected_len, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled tag", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled tag", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- GROUP: With unknown category
++ ("case U0x: disabled and unknown tag", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ])
++ def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++ @pytest.mark.parametrize("case, expected, tags", [
++ # -- GROUP: With positive logic (non-negated tags)
++ ("case P00: 2 disabled tags", True,
++ [ traits.category1_disabled_tag, traits.category1_disabled_tag2]),
++ ("case P01: disabled and enabled tag", False,
++ [ traits.category1_disabled_tag, traits.category1_enabled_tag]),
++ ("case P10: enabled and disabled tag", False,
++ [ traits.category1_enabled_tag, traits.category1_disabled_tag]),
++ ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category1_enabled_tag]),
++ # -- GROUP: With negated tag
++ ("case N00: not-enabled and disabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
++ ("case N01: not-enabled and enabled tag", True,
++ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
++ ("case N10: not-disabled and disabled tag", True,
++ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
++ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
++ ])
++ def test_should_exclude_with__combinations_with_same_category(self,
++ case, expected, tags):
++ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
++ actual_result = tag_matcher.should_exclude_with(tags)
++ assert expected == actual_result, case
++
++
++class TestActiveTags(TestCase):
++ TagMatcher = ActiveTagMatcher
++ traits = Traits4ActiveTagMatcher
++
++ @classmethod
++ def make_tag_matcher(cls):
++ tag_matcher = cls.TagMatcher(cls.traits.value_provider)
++ return tag_matcher
++
++ def setUp(self):
++ self.tag_matcher = self.make_tag_matcher()
++
++ def test_select_active_tags__basics(self):
++ active_tag = "active.with_CATEGORY=VALUE"
++ tags = ["foo", active_tag, "bar"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_tag, active_tag)
++
++ def test_select_active_tags__matches_tag_parts(self):
++ tags = ["active.with_CATEGORY=VALUE"]
++ selected = list(self.tag_matcher.select_active_tags(tags))
++ self.assertEqual(len(selected), 1)
++ selected_tag, selected_match = selected[0]
++ self.assertEqual(selected_match.group("prefix"), "active")
++ self.assertEqual(selected_match.group("category"), "CATEGORY")
++ self.assertEqual(selected_match.group("value"), "VALUE")
++
++ def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self):
++ TagMatcher = self.TagMatcher
++ for tag_prefix in TagMatcher.tag_prefixes:
++ tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix)
++ tags = [ tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 1)
++ selected_tag0 = selected[0][0]
++ self.assertEqual(selected_tag0, tag)
++ self.assertTrue(selected_tag0.startswith(tag_prefix))
++
++ def test_select_active_tags__ignores_invalid_active_tags(self):
++ invalid_active_tags = [
++ ("foo.alice", "case: Normal tag"),
++ ("with_foo=Frank", "case: Subset of an active tag"),
++ ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"),
++ ("only.with_foo.alice", "case: Wrong value_separator"),
++ ]
++ for invalid_tag, case in invalid_active_tags:
++ tags = [ invalid_tag ]
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertEqual(len(selected), 0, case)
++
++ def test_select_active_tags__with_two_tags(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case: Two enabled tags",
++ [traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ ("case: Active enabled and normal tag",
++ [traits.category1_enabled_tag, "foo"]),
++ ("case: Active disabled and normal tag",
++ [traits.category1_disabled_tag, "foo"]),
++ ("case: Active negated and normal tag",
++ [traits.category1_not_enabled_tag, "foo"]),
++ ]
++ for case, tags in test_patterns:
++ selected = self.tag_matcher.select_active_tags(tags)
++ selected = list(selected)
++ self.assertTrue(len(selected) >= 1, case)
++
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
++ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ enabled = True # EXPECTED
++ for tags, case in test_patterns:
++ self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ # XXX-JE-DUPLICATED:
++ traits = self.traits
++ test_patterns = [
++ ("case P00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case P01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case P10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With negated category
++ ("case N00: not-enabled and disabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]),
++ ("case N01: not-enabled and enabled category tags", True,
++ [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]),
++ ("case N10: not-disabled and disabled category tags", True,
++ [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]),
++ ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++
++class TestCompositeTagMatcher(TestCase):
++
++ @staticmethod
++ def count_tag_matcher_with_result(tag_matchers, tags, result_value):
++ count = 0
++ for tag_matcher in tag_matchers:
++ current_result = tag_matcher.should_exclude_with(tags)
++ if current_result == result_value:
++ count += 1
++ return count
++
++ def setUp(self):
++ predicate_false = lambda tags: False
++ predicate_contains_foo = lambda tags: any(x == "foo" for x in tags)
++ self.tag_matcher_false = PredicateTagMatcher(predicate_false)
++ self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo)
++ tag_matchers = [
++ self.tag_matcher_foo,
++ self.tag_matcher_false
++ ]
++ self.ctag_matcher = CompositeTagMatcher(tag_matchers)
++
++ def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self):
++ test_patterns = [
++ ("case: with foo", ["foo", "bar"]),
++ ("case: with foo2", ["foozy", "foo", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(True, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(1, actual_true_count)
++
++ def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self):
++ test_patterns = [
++ ("case: without foo", ["fool", "bar"]),
++ ("case: without foo2", ["foozy", "bar"]),
++ ]
++ for case, tags in test_patterns:
++ actual_result = self.ctag_matcher.should_exclude_with(tags)
++ self.assertEqual(False, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ actual_true_count = self.count_tag_matcher_with_result(
++ self.ctag_matcher.tag_matchers, tags, True)
++ self.assertEqual(0, actual_true_count)
++'''
++
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index a04c1d4..43f5af0 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -128,9 +128,9 @@ class TestActiveTagMatcher2(object):
+ # -- GROUP: With negated tag
+ ("case N00: not-enabled and disabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]),
+- ("case N01: not-enabled and enabled tag", False,
++ ("case N01: not-enabled and enabled tag", True,
+ [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]),
+- ("case N10: not-disabled and disabled tag", False,
++ ("case N10: not-disabled and disabled tag", True,
+ [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]),
+ ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN
+ [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]),
+@@ -138,6 +138,8 @@ class TestActiveTagMatcher2(object):
+ def test_should_exclude_with__combinations_with_same_category(self,
+ case, expected, tags):
+ tag_matcher = self.make_tag_matcher()
++ print("tags: {}".format(tags) )
++ print("tag_matcher.value: {}".format(tag_matcher.value_provider) )
+ actual_result = tag_matcher.should_exclude_with(tags)
+ assert expected == actual_result, case
+
+@@ -224,6 +226,7 @@ class TestActiveTagMatcher1(TestCase):
+
+ def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self):
+ # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED
++ # pylint: disable=line-too-long
+ traits = self.traits
+ test_patterns = [
+ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+@@ -283,7 +286,7 @@ class TestActiveTagMatcher1(TestCase):
+ """
+ traits = self.traits
+ tags = [ traits.unknown_category_tag ]
+- self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual("use.with_UNKNOWN=one", traits.unknown_category_tag)
+ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+
+@@ -376,42 +379,6 @@ class TestPredicateTagMatcher(TestCase):
+ self.assertEqual(False, predicate_always_false(tags))
+
+
+-class TestPredicateTagMatcher(TestCase):
+-
+- def test_exclude_with__mechanics(self):
+- predicate_function_blueprint = lambda tags: False
+- predicate_function = Mock(predicate_function_blueprint)
+- predicate_function.return_value = True
+- tag_matcher = PredicateTagMatcher(predicate_function)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher.should_exclude_with(tags))
+- predicate_function.assert_called_once_with(tags)
+- self.assertEqual(True, predicate_function(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true(self):
+- predicate_always_true = lambda tags: True
+- tag_matcher1 = PredicateTagMatcher(predicate_always_true)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(True, predicate_always_true(tags))
+-
+- def test_should_exclude_with__returns_true_when_predicate_is_true2(self):
+- # -- CASE: Use predicate function instead of lambda.
+- def predicate_contains_foo(tags):
+- return any(x == "foo" for x in tags)
+- tag_matcher2 = PredicateTagMatcher(predicate_contains_foo)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(True, tag_matcher2.should_exclude_with(tags))
+- self.assertEqual(True, predicate_contains_foo(tags))
+-
+- def test_should_exclude_with__returns_false_when_predicate_is_false(self):
+- predicate_always_false = lambda tags: False
+- tag_matcher1 = PredicateTagMatcher(predicate_always_false)
+- tags = [ "foo", "bar" ]
+- self.assertEqual(False, tag_matcher1.should_exclude_with(tags))
+- self.assertEqual(False, predicate_always_false(tags))
+-
+-
+ class TestCompositeTagMatcher(TestCase):
+
+ @staticmethod
diff --git a/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
new file mode 100644
index 000000000..19a8af3f9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
@@ -0,0 +1,56 @@
+From 09d8eddae347bc44e66079e16dc49733d1c220f8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:09:59 +0100
+Subject: [PATCH] FIX: Tests w/ more.features/
+
+---
+ py.requirements/ci.tox.txt | 2 ++
+ py.requirements/ci.travis.txt | 2 ++
+ tox.ini | 4 ++--
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+index 5bee524..387e905 100644
+--- a/py.requirements/ci.tox.txt
++++ b/py.requirements/ci.tox.txt
+@@ -13,3 +13,5 @@ PyHamcrest < 2.0; python_version < '3.0'
+ # -- HINT: path.py => path (python-install-package was renamed for python3)
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
++
++jsonschema
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 372116a..cbc60c0 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -16,6 +16,8 @@ PyHamcrest < 2.0; python_version < '3.0'
+ path.py >= 11.5.0; python_version < '3.5'
+ path >= 13.1.0; python_version >= '3.5'
+
++jsonschema
++
+ # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests.
+ # This problem does not exist if you use pip.
+ linecache2 >= 1.0; python_version < '3.0'
+diff --git a/tox.ini b/tox.ini
+index 8ccb58b..c145b51 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -6,7 +6,7 @@
+ # Use tox to run tasks (tests, ...) in a clean virtual environment.
+ # Afterwards you can run tox in offline mode, like:
+ #
+-# tox -e py27
++# tox -e py38
+ #
+ # Tox can be configured for offline usage.
+ # Initialize local workspace once (download packages, create PyPI index):
+@@ -28,7 +28,7 @@
+
+ [tox]
+ minversion = 2.3
+-envlist = py27, py37, py38, py36, py35, pypy, docs
++envlist = py39, py38, py27, py37, py36, py35, pypy3, pypy, docs
+ skip_missing_interpreters = True
+ sitepackages = False
+ indexserver =
diff --git a/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
new file mode 100644
index 000000000..fb54b8800
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
@@ -0,0 +1,741 @@
+From 5a9343847d8343c7ab78a4576a9507405dd33299 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 22:11:38 +0100
+Subject: [PATCH] FIX: Some regressions with Python 3.9
+
+* async-steps: Use more stable active-tags now.
+* JUnit XML related: Adapt to XML attribute ordering changes since Python 3.8.
+* Prepare active-tags usage for Python 3.10
+* Behave jsonschema: Add some extra elements (related to: gherkin v6 Rule).
+---
+ .gitignore | 1 +
+ CHANGES.rst | 2 +
+ behave/python_feature.py | 81 +++++++++++++++++++
+ etc/json/behave.json-schema | 28 ++++++-
+ .../features/async_dispatch.feature | 7 +-
+ .../async_step/features/async_run.feature | 7 +-
+ examples/async_step/features/environment.py | 16 ++--
+ .../features/steps/_async_steps34.py | 4 +-
+ .../features/steps/async_dispatch_steps.py | 13 +--
+ .../async_step/features/steps/async_steps.py | 6 +-
+ features/environment.py | 12 ++-
+ features/step.async_steps.feature | 64 ++++-----------
+ issue.features/issue0330.feature | 18 +++--
+ issue.features/issue0446.feature | 12 ++-
+ issue.features/issue0457.feature | 12 ++-
+ issue.features/issue0657.feature | 11 +--
+ more.features/environment.py | 10 +--
+ more.features/run_examples.feature | 9 +--
+ 18 files changed, 195 insertions(+), 118 deletions(-)
+ create mode 100644 behave/python_feature.py
+
+diff --git a/.gitignore b/.gitignore
+index 9c5c33d..8d7c28e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -25,3 +25,4 @@ tools/virtualenvs
+ .ropeproject
+ nosetests.xml
+ rerun.txt
++testrun*.json
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 4e20bb8..a3398d8 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -38,6 +38,8 @@ CLARIFICATION:
+
+ FIXED:
+
++* FIXED: Some tests related to python3.9
++* FIXED: active-tag logic if multiple tags with same category exists.
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+diff --git a/behave/python_feature.py b/behave/python_feature.py
+new file mode 100644
+index 0000000..4e134de
+--- /dev/null
++++ b/behave/python_feature.py
+@@ -0,0 +1,81 @@
++# -*- coding: UTF-8 -*-
++"""
++Provides a knowledge database if some Python features are supported
++in the current python version.
++"""
++
++from __future__ import absolute_import
++import sys
++import six
++from behave.tag_matcher import bool_to_string
++
++
++# -----------------------------------------------------------------------------
++# CONSTANTS:
++# -----------------------------------------------------------------------------
++PYTHON_VERSION = sys.version_info[:2]
++
++
++# -----------------------------------------------------------------------------
++# CLASSES:
++# -----------------------------------------------------------------------------
++class PythonFeature(object):
++
++ @staticmethod
++ def has_asyncio_coroutine_decorator():
++ """Indicates if python supports ``@asyncio.coroutine`` decorator.
++
++ EXAMPLE::
++
++ import asyncio
++ @asyncio.coroutine
++ def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++
++ .. since:: Python >= 3.4
++ .. deprecated:: Since Python 3.8 (use async-function instead)
++ """
++ # -- NOTE: @asyncio.coroutine is deprecated in py3.8, removed in py3.10
++ return (3, 4) <= PYTHON_VERSION < (3, 10)
++
++ @staticmethod
++ def has_async_function():
++ """Indicates if python supports async-functions / async-keyword.
++
++ EXAMPLE::
++
++ import asyncio
++ async def async_waits_seconds(duration):
++ yield from asyncio.sleep(duration)
++
++ :returns: True, if this python version supports this feature.
++ .. since:: Python >= 3.5
++ """
++ return (3, 5) <= PYTHON_VERSION
++
++ @classmethod
++ def has_coroutine(cls):
++ return cls.has_async_function() or cls.has_asyncio_coroutine_decorator()
++
++
++# -----------------------------------------------------------------------------
++# SUPPORTED: ACTIVE-TAGS
++# -----------------------------------------------------------------------------
++PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR = PythonFeature.has_asyncio_coroutine_decorator()
++PYTHON_HAS_ASYNC_FUNCTION = PythonFeature.has_async_function()
++PYTHON_HAS_COROUTINE = PythonFeature.has_coroutine()
++ACTIVE_TAG_VALUE_PROVIDER = {
++ "python2": bool_to_string(six.PY2),
++ "python3": bool_to_string(six.PY3),
++ "python.version": "%s.%s" % PYTHON_VERSION,
++ "os": sys.platform.lower(),
++
++ # -- PYTHON FEATURE, like: @use.with_py.feature_asyncio.coroutine
++ "python_has_coroutine": bool_to_string(PYTHON_HAS_COROUTINE),
++ "python_has_asyncio.coroutine_decorator":
++ bool_to_string(PYTHON_HAS_ASYNCIO_COROUTINE_DECORATOR),
++ "python_has_async_function": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++ "python_has_async_keyword": bool_to_string(PYTHON_HAS_ASYNC_FUNCTION),
++}
+diff --git a/etc/json/behave.json-schema b/etc/json/behave.json-schema
+index 9cf3e62..110e240 100644
+--- a/etc/json/behave.json-schema
++++ b/etc/json/behave.json-schema
+@@ -28,10 +28,36 @@
+ "type": "object",
+ "anyOf": [
+ { "$ref": "#/definitions/Background" },
++ { "$ref": "#/definitions/Rule" },
+ { "$ref": "#/definitions/Scenario" },
+ { "$ref": "#/definitions/ScenarioOutline" }
+ ]
+ },
++ "Rule": {
++ "type": "object",
++ "description": "Represents a Rule object.",
++ "properties": {
++ "name": { "type": "string" },
++ "keyword": { "type": "string" },
++ "location": { "type": "string" },
++ "status": { "type": "string" },
++ "tags": { "$ref": "#/definitions/Tags" },
++ "description": { "$ref": "#/definitions/MultiLineText" },
++ "elements": {
++ "type": "array",
++ "items": { "$ref": "#/definitions/RuleElement" }
++ }
++ },
++ "required": [ "name", "keyword", "location" ]
++ },
++ "RuleElement": {
++ "type": "object",
++ "anyOf": [
++ { "$ref": "#/definitions/Scenario" },
++ { "$ref": "#/definitions/ScenarioOutline" },
++ { "$ref": "#/definitions/Background" }
++ ]
++ },
+ "Background": {
+ "type": "object",
+ "properties": {
+@@ -169,4 +195,4 @@
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/examples/async_step/features/async_dispatch.feature b/examples/async_step/features/async_dispatch.feature
+index 18e9869..e0eba1e 100644
+--- a/examples/async_step/features/async_dispatch.feature
++++ b/examples/async_step/features/async_dispatch.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given I dispatch an async-call with param "Alice"
+diff --git a/examples/async_step/features/async_run.feature b/examples/async_step/features/async_run.feature
+index 29b8fa7..8b6e555 100644
+--- a/examples/async_step/features/async_run.feature
++++ b/examples/async_step/features/async_run.feature
+@@ -1,8 +1,5 @@
+-@use.with_python.version=3.4
+-@use.with_python.version=3.5
+-@use.with_python.version=3.6
+-@use.with_python.version=3.7
+-@use.with_python.version=3.8
++@use.with_python_has_async_function=true
++@use.with_python_has_asyncio.coroutine_decorator=true
+ Feature:
+ Scenario:
+ Given an async-step waits 0.3 seconds
+diff --git a/examples/async_step/features/environment.py b/examples/async_step/features/environment.py
+index 02c4d92..3fa9604 100644
+--- a/examples/async_step/features/environment.py
++++ b/examples/async_step/features/environment.py
+@@ -2,7 +2,8 @@
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave.api.runtime_constraint import require_min_python_version
+-import sys
++from behave import python_feature
++
+
+ # -----------------------------------------------------------------------------
+ # REQUIRE: python >= 3.4
+@@ -15,27 +16,24 @@ require_min_python_version("3.4")
+ # -----------------------------------------------------------------------------
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+-def before_all(context):
++def before_all(ctx):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+- setup_active_tag_values(active_tag_value_provider, context.config.userdata)
++ setup_active_tag_values(active_tag_value_provider, ctx.config.userdata)
+
+
+-def before_feature(context, feature):
++def before_feature(ctx, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
+
+-def before_scenario(context, scenario):
++def before_scenario(ctx, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
+diff --git a/examples/async_step/features/steps/_async_steps34.py b/examples/async_step/features/steps/_async_steps34.py
+index 556500f..fc4c8d1 100644
+--- a/examples/async_step/features/steps/_async_steps34.py
++++ b/examples/async_step/features/steps/_async_steps34.py
+@@ -1,5 +1,5 @@
+-# -- REQUIRES: Python >= 3.4 and Python < 3.8
+-# HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++# -- REQUIRES: Python >= 3.4 and Python < 3.10 (removed in: 3.10)
++# HINT: @asyncio.coroutine decorator is deprecated since python 3.8
+ # USE: Async generator/coroutine instead.
+
+ from behave import step
+diff --git a/examples/async_step/features/steps/async_dispatch_steps.py b/examples/async_step/features/steps/async_dispatch_steps.py
+index 222e54d..def9616 100644
+--- a/examples/async_step/features/steps/async_dispatch_steps.py
++++ b/examples/async_step/features/steps/async_dispatch_steps.py
+@@ -1,8 +1,9 @@
+ # -*- coding: UTF-8 -*-
+ # REQUIRES: Python >= 3.4/3.5
+-import sys
++
+ from behave import given, then, step
+ from behave.api.async_step import use_or_create_async_context
++from behave.python_feature import PythonFeature
+ from hamcrest import assert_that, equal_to, empty
+ import asyncio
+
+@@ -10,13 +11,15 @@ import asyncio
+ # ---------------------------------------------------------------------------
+ # ASYNC EXAMPLE FUNCTION:
+ # ---------------------------------------------------------------------------
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++if PythonFeature.has_async_function():
++ # -- USE: async-function as coroutine-function
++ # SINCE: Python 3.5 (preferred)
+ async def async_func(param):
+ await asyncio.sleep(0.2)
+ return str(param).upper()
+-else:
+- # -- HINT: Decorator @asyncio.coroutine is prohibited in python 3.8
++elif PythonFeature.has_asyncio_coroutine_decorator():
++ # -- USE: @asyncio.coroutine decorator
++ # SINCE: Python 3.4, deprecated since Python 3.8, removed in Python 3.10
+ @asyncio.coroutine
+ def async_func(param):
+ yield from asyncio.sleep(0.2)
+diff --git a/examples/async_step/features/steps/async_steps.py b/examples/async_step/features/steps/async_steps.py
+index dc03c72..33d2392 100644
+--- a/examples/async_step/features/steps/async_steps.py
++++ b/examples/async_step/features/steps/async_steps.py
+@@ -5,8 +5,8 @@
+ from __future__ import absolute_import
+ import sys
+
+-python_version = "%s.%s" % sys.version_info[:2]
+-if python_version >= "3.5":
++python_version = sys.version_info[:2]
++if python_version >= (3, 5):
+ import _async_steps35
+-elif python_version == "3.4":
++elif python_version == (3, 4):
+ import _async_steps34
+diff --git a/features/environment.py b/features/environment.py
+index 3769ee4..6faf4e2 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -4,22 +4,19 @@
+ from __future__ import absolute_import, print_function
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
++from behave import python_feature
+ import platform
+ import sys
+-import six
++
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+ active_tag_value_provider = {
+- "python2": str(six.PY2).lower(),
+- "python3": str(six.PY3).lower(),
+- "python.version": python_version,
+ # -- python.implementation: cpython, pypy, jython, ironpython
+ "python.implementation": platform.python_implementation().lower(),
+ "pypy": str("__pypy__" in sys.modules).lower(),
+- "os": sys.platform,
+ }
++active_tag_value_provider.update(python_feature.ACTIVE_TAG_VALUE_PROVIDER)
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+@@ -27,7 +24,7 @@ def print_active_tags_summary():
+ active_tag_data = active_tag_value_provider
+ print("ACTIVE-TAG SUMMARY:")
+ print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
++ print("use.with_os=%s" % active_tag_data.get("os"))
+ print()
+
+
+@@ -53,6 +50,7 @@ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ # -----------------------------------------------------------------------------
+ # SPECIFIC FUNCTIONALITY:
+ # -----------------------------------------------------------------------------
+diff --git a/features/step.async_steps.feature b/features/step.async_steps.feature
+index 06709d9..5919397 100644
+--- a/features/step.async_steps.feature
++++ b/features/step.async_steps.feature
+@@ -1,4 +1,5 @@
+ @not.with_python2=true
++@use.with_python_has_coroutine=true
+ Feature: Async-Test Support (async-step, ...)
+
+ As a test writer and step provider
+@@ -16,11 +17,11 @@ Feature: Async-Test Support (async-step, ...)
+ . * an async-function as coroutine using async/await keywords (Python 3.5)
+ . * an async-function tagged with @asyncio.coroutine and using "yield from"
+ .
+- . # -- EXAMPLE CASE 1 (since Python 3.5):
++ . # -- EXAMPLE CASE 1 (since Python 3.5; preferred):
+ . async def coroutine1(duration):
+ . await asyncio.sleep(duration)
+ .
+- . # -- EXAMPLE CASE 2 (since Python 3.4):
++ . # -- EXAMPLE CASE 2 (since Python 3.4; deprecated since Python 3.8; removed in Python 3.10):
+ . @asyncio.coroutine
+ . def coroutine2(duration):
+ . yield from asyncio.sleep(duration)
+@@ -30,12 +31,8 @@ Feature: Async-Test Support (async-step, ...)
+ . The async-step can directly interact with other async-functions.
+
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use async-step with @async_run_until_complete (async; requires: py.version >= 3.5)
+ Given a new working directory
+ And a file named "features/steps/async_steps35.py" with:
+ """
+@@ -63,13 +60,8 @@ Feature: Async-Test Support (async-step, ...)
+ """
+
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-step with @async_run_until_complete (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-step with @async_run_until_complete (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps34.py" with:
+ """
+@@ -97,12 +89,8 @@ Feature: Async-Test Support (async-step, ...)
+ Given an async-step waits 0.3 seconds ... passed in 0.3
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async)
++ @use.with_python_has_async_function=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout35.py" with:
+ """
+@@ -135,13 +123,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+@@ -180,13 +164,9 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: XFAIL in async-step
+ """
+
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step raises error
++ Scenario: Use @async_run_until_complete and async-step raises error (async-function)
+ Given a new working directory
+ And a file named "features/steps/async_steps_exception35.py" with:
+ """
+@@ -225,13 +205,8 @@ Feature: Async-Test Support (async-step, ...)
+ raise RuntimeError("XFAIL in async-step")
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine)
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_steps_timeout34.py" with:
+ """
+@@ -265,13 +240,8 @@ Feature: Async-Test Support (async-step, ...)
+ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2
+ """
+
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
+- Scenario: Use async-dispatch and async-collect concepts
++ @use.with_python_has_asyncio.coroutine_decorator=true
++ Scenario: Use async-dispatch and async-collect concepts (@asyncio.coroutine)
+ Given a new working directory
+ And a file named "features/steps/async_dispatch_steps.py" with:
+ """
+diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
+index be4d378..56ac238 100644
+--- a/issue.features/issue0330.feature
++++ b/issue.features/issue0330.feature
+@@ -72,7 +72,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -87,7 +88,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped feature is created with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped feature is created with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+ Then it should pass with:
+ """
+@@ -104,7 +106,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -125,7 +128,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --no-skipped"
+ Then it should pass with:
+ """
+@@ -149,7 +153,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @not.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version < 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+@@ -171,7 +176,8 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
++ @use.with_python.version=3.10
++ Scenario: Junit report for skipped scenario is shown and counted with --show-skipped (py.version >= 3.8)
+ When I run "behave --junit -t @tag1 --show-skipped"
+ Then it should pass with:
+ """
+diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
+index 12de37a..d7db764 100644
+--- a/issue.features/issue0446.feature
++++ b/issue.features/issue0446.feature
+@@ -60,7 +60,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -90,7 +91,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in before_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in before_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/before_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -124,7 +126,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @not.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version < 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+@@ -156,7 +159,8 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Hook error in after_scenario()
++ @use.with_python.version=3.10
++ Scenario: Hook error in after_scenario() (py.version >= 3.8)
+ When I run "behave -f plain --junit features/after_scenario_failure.feature"
+ Then it should fail with:
+ """
+diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
+index 6d2f48f..c14c7a4 100644
+--- a/issue.features/issue0457.feature
++++ b/issue.features/issue0457.feature
+@@ -26,7 +26,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -48,7 +49,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use failing assertation in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use failing assertation in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails1.feature" with:
+ """
+ Feature:
+@@ -73,7 +75,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @not.with_python.version=3.8
+ @not.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @not.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version < 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+@@ -96,7 +99,8 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
+
+ @use.with_python.version=3.8
+ @use.with_python.version=3.9
+- Scenario: Use exception in a JUnit XML report
++ @use.with_python.version=3.10
++ Scenario: Use exception in a JUnit XML report (py.version >= 3.8)
+ Given a file named "features/fails2.feature" with:
+ """
+ Feature:
+diff --git a/issue.features/issue0657.feature b/issue.features/issue0657.feature
+index aeaefd2..a674a26 100644
+--- a/issue.features/issue0657.feature
++++ b/issue.features/issue0657.feature
+@@ -1,15 +1,10 @@
+-@not.with_python2=true
+ @issue
++@not.with_python2=true
+ Feature: Issue #657 -- Allow async steps with timeouts to fail when they raise exceptions
+
+-
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- @use.with_python.version=3.9
++ @use.with_python_has_async_function=true
+ @async_step_fails
+- Scenario: Use @async_run_until_complete and async-step fails
++ Scenario: Use @async_run_until_complete and async-step fails (py.version >= 3.8)
+ Given a new working directory
+ And a file named "features/steps/async_steps_fails35.py" with:
+ """
+diff --git a/more.features/environment.py b/more.features/environment.py
+index 9d4302b..14d904c 100644
+--- a/more.features/environment.py
++++ b/more.features/environment.py
+@@ -1,16 +1,14 @@
+ # -*- coding: UTF-8 -*-
+
+ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
+-import sys
++from behave import python_feature
+
+ # -- MATCHES ANY TAGS: @use.with_{category}={value}
+ # NOTE: active_tag_value_provider provides category values for active tags.
+-python_version = "%s.%s" % sys.version_info[:2]
+-active_tag_value_provider = {
+- "python.version": python_version,
+-}
++active_tag_value_provider = python_feature.ACTIVE_TAG_VALUE_PROVIDER.copy()
+ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
++
+ # -----------------------------------------------------------------------------
+ # HOOKS:
+ # -----------------------------------------------------------------------------
+@@ -18,10 +16,12 @@ def before_all(context):
+ # -- SETUP ACTIVE-TAG MATCHER (with userdata):
+ setup_active_tag_values(active_tag_value_provider, context.config.userdata)
+
++
+ def before_feature(context, feature):
+ if active_tag_matcher.should_exclude_with(feature.tags):
+ feature.skip(reason=active_tag_matcher.exclude_reason)
+
++
+ def before_scenario(context, scenario):
+ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+ scenario.skip(reason=active_tag_matcher.exclude_reason)
+diff --git a/more.features/run_examples.feature b/more.features/run_examples.feature
+index 4778866..14acf98 100644
+--- a/more.features/run_examples.feature
++++ b/more.features/run_examples.feature
+@@ -29,13 +29,8 @@ Feature: Ensure that all examples are usable
+ features/rule_fails.feature:16 F2 -- Fails
+ """
+
+-
+- @use.with_python.version=3.4
+- @use.with_python.version=3.5
+- @use.with_python.version=3.6
+- @use.with_python.version=3.7
+- @use.with_python.version=3.8
+- Scenario: examples/async_step (needs: py34 or newer)
++ @use.with_python_has_coroutine=true
++ Scenario: examples/async_step (requires: python.version >= 3.4)
+ Given I use the directory "examples/async_step" as working directory
+ When I run "behave features/"
+ Then it should pass
diff --git a/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
new file mode 100644
index 000000000..5c363f735
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
@@ -0,0 +1,84 @@
+From dc4f2714b47a0fb3456e02cf69087eaba3f1ff56 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 9 Jan 2021 23:48:52 +0100
+Subject: [PATCH] docs: Update new and noteworthy
+
+* Add section on improved active-tag logic
+---
+ docs/conf.py | 2 +-
+ docs/new_and_noteworthy_v1.2.7.rst | 45 ++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 1579a36..cece86a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -108,7 +108,7 @@ if USE_SPHINX_INTERNATIONAL:
+ # -----------------------------------------------------------------------------
+ project = u"behave"
+ authors = u"Jens Engel, Benno Rice and Richard Jones"
+-copyright = u"2012-2020, %s" % authors
++copyright = u"2012-2021, %s" % authors
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index b7242f7..2eb94ad 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -9,6 +9,7 @@ Summary:
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+ * `Support for emojis in feature files and steps`_
++* `Improve Active-Tags Logic`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -152,3 +153,47 @@ Support for Emojis in Feature Files and Steps
+ .. literalinclude:: ../features/steps/i18n_emoji_steps.py
+ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
+ :language: python
++
++
++Improve Active-Tags Logic
++-------------------------------------------------------------------------------
++
++The active-tag computation logic was slightly changed (and fixed):
++
++* if multiple active-tags with same category are used
++* combination of positive active-tags (``use.with_{category}={value}``) and
++ negative active-tags (``not.with_{category}={value}``) with same category
++ are now supported
++
++All active-tags with same category are combined into one category tag-group.
++The following logical expression is used for active-tags with the same category::
++
++ category_tag_group.enabled := positive-tag-expression and not negative-tag-expression
++ positive-tag-expression := enabled(tag1) or enabled(tag2) or ...
++ negative-tag-expression := enabled(tag3) or enabled(tag4) or ...
++ tag1, tag2 are positive-tags, like @use.with_category=value
++ tag3, tag4 are negative-tags, like @not.with_category=value
++
++
++EXAMPLE:
++
++.. code-block:: gherkin
++
++ Feature: Active-Tag Example
++
++ @use.with_browser=Safari
++ @use.with_browser=Chrome
++ @not.with_browser=Firefox
++ Scenario: Use one active-tag group/category
++
++ HINT: Only executed with web browser Safari and Chrome, Firefox is explicitly excluded.
++ ...
++
++ @use.with_browser=Firefox
++ @use.with_os=linux
++ @use.with_os=darwin
++ Scenario: Use two active-tag groups/categories
++
++ HINT 1: Only executed with browser: Firefox
++ HINT 2: Only executed on OS: Linux and Darwin (macOS)
++ ...
diff --git a/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
new file mode 100644
index 000000000..c19ad08ae
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
@@ -0,0 +1,278 @@
+From 27c658d69cd4a0b90745a276a4a09f237993b596 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 10 Jan 2021 13:40:26 +0100
+Subject: [PATCH] * Add diagnostic helper function to print the current values
+ of active-tags. * Add helper classes for active-tag value providers.
+
+---
+ behave/compat/collections.py | 25 ++++++
+ behave/tag_matcher.py | 145 +++++++++++++++++++++++++++++++---
+ features/environment.py | 9 +--
+ issue.features/environment.py | 9 +--
+ 4 files changed, 166 insertions(+), 22 deletions(-)
+
+diff --git a/behave/compat/collections.py b/behave/compat/collections.py
+index 00444f4..f4eea2c 100644
+--- a/behave/compat/collections.py
++++ b/behave/compat/collections.py
+@@ -18,3 +18,28 @@ except ImportError: # pragma: no cover
+ warnings.warn(message)
+ # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
+ OrderedDict = dict
++
++try:
++ from collections import UserDict
++except ImportError: # pragma: no cover
++ class UserDict(object):
++ """Emulate collections.UserDict class in python3."""
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ self.data = data
++
++ def __len__(self):
++ return len(self.data)
++
++ def __iter__(self):
++ return len(self.data)
++
++ def keys(self):
++ return self.data.keys()
++
++ def values(self):
++ return self.data.values()
++
++ def items(self):
++ return self.data.items()
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index e2b1e82..78d7061 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -4,17 +4,16 @@ Contains classes and functionality to provide the active-tag mechanism.
+ Active-tags provide a skip-if logic based on tags in feature files.
+ """
+
+-from __future__ import absolute_import
++from __future__ import absolute_import, print_function
+ import re
+-import operator
+ import six
++from ._types import Unknown
++from .compat.collections import UserDict
+
+
+-def bool_to_string(value):
+- """Converts a Boolean value into its normalized string representation."""
+- return str(bool(value)).lower()
+-
+-
++# -----------------------------------------------------------------------------
++# CLASSES FOR: Active-Tags and ActiveTagMatchers
++# -----------------------------------------------------------------------------
+ class TagMatcher(object):
+ """Abstract base class that defines the TagMatcher protocol."""
+
+@@ -220,8 +219,8 @@ class ActiveTagMatcher(TagMatcher):
+ # -- CASE: Empty group is always enabled (CORNER-CASE).
+ return True
+
+- current_value = self.value_provider.get(group_category, None)
+- if current_value is None and self.ignore_unknown_categories:
++ current_value = self.value_provider.get(group_category, Unknown)
++ if current_value is Unknown and self.ignore_unknown_categories:
+ # -- CASE: Unknown category, ignore it.
+ return True
+
+@@ -320,6 +319,115 @@ class CompositeTagMatcher(TagMatcher):
+ return False
+
+
++# -----------------------------------------------------------------------------
++# ACTIVE TAG VALUE PROVIDER CLASSES:
++# -----------------------------------------------------------------------------
++class IActiveTagValueProvider(object):
++ """Protocol/Interface for active-tag value providers."""
++
++ def get(self, category, default=None):
++ return NotImplemented
++
++
++class ActiveTagValueProvider(UserDict):
++ def __init__(self, data=None):
++ if data is None:
++ data = {}
++ UserDict.__init__(self, data)
++
++ @staticmethod
++ def use_value(value):
++ if callable(value):
++ # -- RE-EVALUATE VALUE: Each time
++ value_func = value
++ value = value_func()
++ return value
++
++ def __getitem__(self, name):
++ value = self.data[name]
++ return self.use_value(value)
++
++ def get(self, category, default=None):
++ value = self.data.get(category, default)
++ return self.use_value(value)
++
++ def values(self):
++ for value in self.data.values(self):
++ yield self.use_value(value)
++
++ def items(self):
++ for category, value in self.data.items():
++ yield (category, self.use_value(value))
++
++ def categories(self):
++ return self.keys()
++
++
++class CompositeActiveTagValueProvider(ActiveTagValueProvider):
++ """Provides a composite helper class to resolve active-tag values
++ from a list of value-providers.
++ """
++
++ def __init__(self, value_providers=None):
++ if value_providers is None:
++ value_providers = []
++ super(CompositeActiveTagValueProvider, self).__init__()
++ self.value_providers = list(value_providers)
++
++ def get(self, category, default=None):
++ # -- FIRST: Check category cached-map (=self.data)
++ value = self.data.get(category, Unknown)
++ if value is Unknown:
++ # -- NOT DISCOVERED: Search over value_providers.
++ for value_provider in self.value_providers:
++ value = value_provider.get(category, Unknown)
++ if value is Unknown:
++ continue
++
++ # -- FOUND CATEGORY:
++ self.data[category] = value
++ break
++ # -- FOUND-CATEGORY or NOT-FOUND:
++ if value is Unknown:
++ value = default
++
++ return self.use_value(value)
++
++ # -- MORE: Provide a dict-like interface.
++ def keys(self):
++ for value_provider in self.value_providers:
++ try:
++ for category in value_provider.keys():
++ yield category
++ except AttributeError:
++ # -- keys() method not supported.
++ pass
++
++ def values(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield value
++
++ def items(self):
++ for category in self.keys():
++ value = self.get(category)
++ yield (category, value)
++
++
++
++# -----------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# -----------------------------------------------------------------------------
++def bool_to_string(value):
++ """Converts a boolean active-tag value into its normalized
++ string representation.
++
++ :param value: Boolean value to use (or value converted into bool).
++ :returns: Boolean value converted into a normalized string.
++ """
++ return str(bool(value)).lower()
++
++
+ def setup_active_tag_values(active_tag_values, data):
+ """Setup/update active_tag values with dict-like data.
+ Only values for keys that are already present are updated.
+@@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
++
++
++def print_active_tags(active_tag_value_provider, categories=None):
++ """Print a summary of the current active-tag values."""
++ if categories is None:
++ try:
++ categories = list(active_tag_value_provider)
++ except TypeError: # TypeError: object is not iterable
++ categories = []
++
++ active_tag_data = active_tag_value_provider
++ print("ACTIVE-TAGS:")
++ for category in categories:
++ active_tag_value = active_tag_data.get(category)
++ print("use.with_{category}={value}".format(
++ category=category, value=active_tag_value))
++
++ # -- FINALLY: TRAILING NEW-LINE
++ print()
+diff --git a/features/environment.py b/features/environment.py
+index 6faf4e2..72ebaa0 100644
+--- a/features/environment.py
++++ b/features/environment.py
+@@ -2,7 +2,8 @@
+ # FILE: features/environemnt.py
+
+ from __future__ import absolute_import, print_function
+-from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
++from behave.tag_matcher import \
++ ActiveTagMatcher, setup_active_tag_values, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ from behave import python_feature
+ import platform
+@@ -21,11 +22,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # -----------------------------------------------------------------------------
+diff --git a/issue.features/environment.py b/issue.features/environment.py
+index 7e48ee0..ab85e6c 100644
+--- a/issue.features/environment.py
++++ b/issue.features/environment.py
+@@ -13,7 +13,7 @@ import sys
+ import platform
+ import os.path
+ import six
+-from behave.tag_matcher import ActiveTagMatcher
++from behave.tag_matcher import ActiveTagMatcher, print_active_tags
+ from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
+ # PREPARED: from behave.tag_matcher import setup_active_tag_values
+
+@@ -94,12 +94,7 @@ active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
+
+
+ def print_active_tags_summary():
+- active_tag_data = active_tag_value_provider
+- print("ACTIVE-TAG SUMMARY:")
+- print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+- # print("use.with_platform=%s" % active_tag_data.get("platform"))
+- # print("use.with_os=%s" % active_tag_data.get("os"))
+- print()
++ print_active_tags(active_tag_value_provider, ["python.version", "os"])
+
+
+ # ---------------------------------------------------------------------------
diff --git a/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
new file mode 100644
index 000000000..88ec3fb87
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
@@ -0,0 +1,541 @@
+From c89cdd7efe73df3c3107abaecea62c20a4cebc97 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:21:44 +0100
+Subject: [PATCH] UPDATE: i18n/gherkin-languages.json from cucumber repository.
+
+CONTAINS: PR #827 (Estonian language updates contained)
+LAST COMMIT: 2021-01-07 (in Cucumber repository)
+LANGUAGE CHANGES:
+
+* Swedish: Rule keyword
+* Telugu: Fixing ISO 639-1 code
+* Russian: Rule keyword
+* Hebrew: Rule keyword
+* German: Addition keywords
+* Estonian: Rule keyword, ScenarioOutline keyword
+* Indonesian: Given keywords, whitespace
+---
+ behave/i18n.py | 92 ++++++++++++------
+ etc/gherkin/gherkin-languages.json | 145 +++++++++++++++++++++++++----
+ features/cmdline.lang_list.feature | 64 +++++++++----
+ issue.features/issue0309.feature | 2 +-
+ 4 files changed, 240 insertions(+), 63 deletions(-)
+
+diff --git a/behave/i18n.py b/behave/i18n.py
+index 2781afe..a572626 100644
+--- a/behave/i18n.py
++++ b/behave/i18n.py
+@@ -188,16 +188,19 @@ languages = \
+ 'then': ['* ', 'Så '],
+ 'when': ['* ', 'Når ']},
+ 'de': {'and': ['* ', 'Und '],
+- 'background': ['Grundlage'],
++ 'background': ['Grundlage',
++ 'Hintergrund',
++ 'Voraussetzungen',
++ 'Vorbedingungen'],
+ 'but': ['* ', 'Aber '],
+ 'examples': ['Beispiele'],
+- 'feature': ['Funktionalität'],
++ 'feature': ['Funktionalität', 'Funktion'],
+ 'given': ['* ', 'Angenommen ', 'Gegeben sei ', 'Gegeben seien '],
+ 'name': 'German',
+ 'native': 'Deutsch',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Regel'],
+ 'scenario': ['Beispiel', 'Szenario'],
+- 'scenario_outline': ['Szenariogrundriss'],
++ 'scenario_outline': ['Szenariogrundriss', 'Szenarien'],
+ 'then': ['* ', 'Dann '],
+ 'when': ['* ', 'Wenn ']},
+ 'el': {'and': ['* ', 'Και '],
+@@ -344,9 +347,9 @@ languages = \
+ 'given': ['* ', 'Eeldades '],
+ 'name': 'Estonian',
+ 'native': 'eesti keel',
+- 'rule': ['Rule'],
++ 'rule': ['Reegel'],
+ 'scenario': ['Juhtum', 'Stsenaarium'],
+- 'scenario_outline': ['Raamstjuhtum', 'Raamstsenaarium'],
++ 'scenario_outline': ['Raamjuhtum', 'Raamstsenaarium'],
+ 'then': ['* ', 'Siis '],
+ 'when': ['* ', 'Kui ']},
+ 'fa': {'and': ['* ', 'و '],
+@@ -455,7 +458,7 @@ languages = \
+ 'given': ['* ', 'בהינתן '],
+ 'name': 'Hebrew',
+ 'native': 'עברית',
+- 'rule': ['Rule'],
++ 'rule': ['כלל'],
+ 'scenario': ['דוגמא', 'תרחיש'],
+ 'scenario_outline': ['תבנית תרחיש'],
+ 'then': ['* ', 'אז ', 'אזי '],
+@@ -518,17 +521,22 @@ languages = \
+ 'then': ['* ', 'Akkor '],
+ 'when': ['* ', 'Majd ', 'Ha ', 'Amikor ']},
+ 'id': {'and': ['* ', 'Dan '],
+- 'background': ['Dasar'],
+- 'but': ['* ', 'Tapi '],
+- 'examples': ['Contoh'],
++ 'background': ['Dasar', 'Latar Belakang'],
++ 'but': ['* ', 'Tapi ', 'Tetapi '],
++ 'examples': ['Contoh', 'Misal'],
+ 'feature': ['Fitur'],
+- 'given': ['* ', 'Dengan '],
++ 'given': ['* ',
++ 'Dengan ',
++ 'Diketahui ',
++ 'Diasumsikan ',
++ 'Bila ',
++ 'Jika '],
+ 'name': 'Indonesian',
+ 'native': 'Bahasa Indonesia',
+- 'rule': ['Rule'],
++ 'rule': ['Rule', 'Aturan'],
+ 'scenario': ['Skenario'],
+- 'scenario_outline': ['Skenario konsep'],
+- 'then': ['* ', 'Maka '],
++ 'scenario_outline': ['Skenario konsep', 'Garis-Besar Skenario'],
++ 'then': ['* ', 'Maka ', 'Kemudian '],
+ 'when': ['* ', 'Ketika ']},
+ 'is': {'and': ['* ', 'Og '],
+ 'background': ['Bakgrunnur'],
+@@ -699,6 +707,32 @@ languages = \
+ 'scenario_outline': ['Сценарын төлөвлөгөө'],
+ 'then': ['* ', 'Тэгэхэд ', 'Үүний дараа '],
+ 'when': ['* ', 'Хэрэв ']},
++ 'mr': {'and': ['* ', 'आणि ', 'तसेच '],
++ 'background': ['पार्श्वभूमी'],
++ 'but': ['* ', 'पण ', 'परंतु '],
++ 'examples': ['उदाहरण'],
++ 'feature': ['वैशिष्ट्य', 'सुविधा'],
++ 'given': ['* ', 'जर', 'दिलेल्या प्रमाणे '],
++ 'name': 'Marathi',
++ 'native': 'मराठी',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'मग ', 'तेव्हा '],
++ 'when': ['* ', 'जेव्हा ']},
++ 'ne': {'and': ['* ', 'र ', 'अनी '],
++ 'background': ['पृष्ठभूमी'],
++ 'but': ['* ', 'तर '],
++ 'examples': ['उदाहरण', 'उदाहरणहरु'],
++ 'feature': ['सुविधा', 'विशेषता'],
++ 'given': ['* ', 'दिइएको ', 'दिएको ', 'यदि '],
++ 'name': 'Nepali',
++ 'native': 'नेपाली',
++ 'rule': ['नियम'],
++ 'scenario': ['परिदृश्य'],
++ 'scenario_outline': ['परिदृश्य रूपरेखा'],
++ 'then': ['* ', 'त्यसपछि ', 'अनी '],
++ 'when': ['* ', 'जब ']},
+ 'nl': {'and': ['* ', 'En '],
+ 'background': ['Achtergrond'],
+ 'but': ['* ', 'Maar '],
+@@ -797,7 +831,7 @@ languages = \
+ 'given': ['* ', 'Допустим ', 'Дано ', 'Пусть '],
+ 'name': 'Russian',
+ 'native': 'русский',
+- 'rule': ['Rule'],
++ 'rule': ['Правило'],
+ 'scenario': ['Пример', 'Сценарий'],
+ 'scenario_outline': ['Структура сценария'],
+ 'then': ['* ', 'То ', 'Затем ', 'Тогда '],
+@@ -873,7 +907,7 @@ languages = \
+ 'given': ['* ', 'Givet '],
+ 'name': 'Swedish',
+ 'native': 'Svenska',
+- 'rule': ['Rule'],
++ 'rule': ['Regel'],
+ 'scenario': ['Scenario'],
+ 'scenario_outline': ['Abstrakt Scenario', 'Scenariomall'],
+ 'then': ['* ', 'Så '],
+@@ -891,6 +925,19 @@ languages = \
+ 'scenario_outline': ['காட்சி சுருக்கம்', 'காட்சி வார்ப்புரு'],
+ 'then': ['* ', 'அப்பொழுது '],
+ 'when': ['* ', 'எப்போது ']},
++ 'te': {'and': ['* ', 'మరియు '],
++ 'background': ['నేపథ్యం'],
++ 'but': ['* ', 'కాని '],
++ 'examples': ['ఉదాహరణలు'],
++ 'feature': ['గుణము'],
++ 'given': ['* ', 'చెప్పబడినది '],
++ 'name': 'Telugu',
++ 'native': 'తెలుగు',
++ 'rule': ['Rule'],
++ 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
++ 'scenario_outline': ['కథనం'],
++ 'then': ['* ', 'అప్పుడు '],
++ 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'th': {'and': ['* ', 'และ '],
+ 'background': ['แนวคิด'],
+ 'but': ['* ', 'แต่ '],
+@@ -904,19 +951,6 @@ languages = \
+ 'scenario_outline': ['สรุปเหตุการณ์', 'โครงสร้างของเหตุการณ์'],
+ 'then': ['* ', 'ดังนั้น '],
+ 'when': ['* ', 'เมื่อ ']},
+- 'tl': {'and': ['* ', 'మరియు '],
+- 'background': ['నేపథ్యం'],
+- 'but': ['* ', 'కాని '],
+- 'examples': ['ఉదాహరణలు'],
+- 'feature': ['గుణము'],
+- 'given': ['* ', 'చెప్పబడినది '],
+- 'name': 'Telugu',
+- 'native': 'తెలుగు',
+- 'rule': ['Rule'],
+- 'scenario': ['ఉదాహరణ', 'సన్నివేశం'],
+- 'scenario_outline': ['కథనం'],
+- 'then': ['* ', 'అప్పుడు '],
+- 'when': ['* ', 'ఈ పరిస్థితిలో ']},
+ 'tlh': {'and': ['* ', "'ej ", 'latlh '],
+ 'background': ["mo'"],
+ 'but': ['* ', "'ach ", "'a "],
+diff --git a/etc/gherkin/gherkin-languages.json b/etc/gherkin/gherkin-languages.json
+index 29cbca1..6069664 100644
+--- a/etc/gherkin/gherkin-languages.json
++++ b/etc/gherkin/gherkin-languages.json
+@@ -605,7 +605,10 @@
+ "Und "
+ ],
+ "background": [
+- "Grundlage"
++ "Grundlage",
++ "Hintergrund",
++ "Voraussetzungen",
++ "Vorbedingungen"
+ ],
+ "but": [
+ "* ",
+@@ -615,7 +618,8 @@
+ "Beispiele"
+ ],
+ "feature": [
+- "Funktionalität"
++ "Funktionalität",
++ "Funktion"
+ ],
+ "given": [
+ "* ",
+@@ -626,14 +630,16 @@
+ "name": "German",
+ "native": "Deutsch",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Regel"
+ ],
+ "scenario": [
+ "Beispiel",
+ "Szenario"
+ ],
+ "scenarioOutline": [
+- "Szenariogrundriss"
++ "Szenariogrundriss",
++ "Szenarien"
+ ],
+ "then": [
+ "* ",
+@@ -1127,14 +1133,14 @@
+ "name": "Estonian",
+ "native": "eesti keel",
+ "rule": [
+- "Rule"
++ "Reegel"
+ ],
+ "scenario": [
+ "Juhtum",
+ "Stsenaarium"
+ ],
+ "scenarioOutline": [
+- "Raamstjuhtum",
++ "Raamjuhtum",
+ "Raamstsenaarium"
+ ],
+ "then": [
+@@ -1465,7 +1471,7 @@
+ "name": "Hebrew",
+ "native": "עברית",
+ "rule": [
+- "Rule"
++ "כלל"
+ ],
+ "scenario": [
+ "דוגמא",
+@@ -1692,36 +1698,46 @@
+ "Dan "
+ ],
+ "background": [
+- "Dasar"
++ "Dasar",
++ "Latar Belakang"
+ ],
+ "but": [
+ "* ",
+- "Tapi "
++ "Tapi ",
++ "Tetapi "
+ ],
+ "examples": [
+- "Contoh"
++ "Contoh",
++ "Misal"
+ ],
+ "feature": [
+ "Fitur"
+ ],
+ "given": [
+ "* ",
+- "Dengan "
++ "Dengan ",
++ "Diketahui ",
++ "Diasumsikan ",
++ "Bila ",
++ "Jika "
+ ],
+ "name": "Indonesian",
+ "native": "Bahasa Indonesia",
+ "rule": [
+- "Rule"
++ "Rule",
++ "Aturan"
+ ],
+ "scenario": [
+ "Skenario"
+ ],
+ "scenarioOutline": [
+- "Skenario konsep"
++ "Skenario konsep",
++ "Garis-Besar Skenario"
+ ],
+ "then": [
+ "* ",
+- "Maka "
++ "Maka ",
++ "Kemudian "
+ ],
+ "when": [
+ "* ",
+@@ -2330,6 +2346,54 @@
+ "Хэрэв "
+ ]
+ },
++ "ne": {
++ "and": [
++ "* ",
++ "र ",
++ "अनी "
++ ],
++ "background": [
++ "पृष्ठभूमी"
++ ],
++ "but": [
++ "* ",
++ "तर "
++ ],
++ "examples": [
++ "उदाहरण",
++ "उदाहरणहरु"
++ ],
++ "feature": [
++ "सुविधा",
++ "विशेषता"
++ ],
++ "given": [
++ "* ",
++ "दिइएको ",
++ "दिएको ",
++ "यदि "
++ ],
++ "name": "Nepali",
++ "native": "नेपाली",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "त्यसपछि ",
++ "अनी "
++ ],
++ "when": [
++ "* ",
++ "जब "
++ ]
++ },
+ "nl": {
+ "and": [
+ "* ",
+@@ -2665,7 +2729,7 @@
+ "name": "Russian",
+ "native": "русский",
+ "rule": [
+- "Rule"
++ "Правило"
+ ],
+ "scenario": [
+ "Пример",
+@@ -2933,7 +2997,7 @@
+ "name": "Swedish",
+ "native": "Svenska",
+ "rule": [
+- "Rule"
++ "Regel"
+ ],
+ "scenario": [
+ "Scenario"
+@@ -3046,7 +3110,7 @@
+ "เมื่อ "
+ ]
+ },
+- "tl": {
++ "te": {
+ "and": [
+ "* ",
+ "మరియు "
+@@ -3510,5 +3574,52 @@
+ "* ",
+ "當"
+ ]
++ },
++ "mr": {
++ "and": [
++ "* ",
++ "आणि ",
++ "तसेच "
++ ],
++ "background": [
++ "पार्श्वभूमी"
++ ],
++ "but": [
++ "* ",
++ "पण ",
++ "परंतु "
++ ],
++ "examples": [
++ "उदाहरण"
++ ],
++ "feature": [
++ "वैशिष्ट्य",
++ "सुविधा"
++ ],
++ "given": [
++ "* ",
++ "जर",
++ "दिलेल्या प्रमाणे "
++ ],
++ "name": "Marathi",
++ "native": "मराठी",
++ "rule": [
++ "नियम"
++ ],
++ "scenario": [
++ "परिदृश्य"
++ ],
++ "scenarioOutline": [
++ "परिदृश्य रूपरेखा"
++ ],
++ "then": [
++ "* ",
++ "मग ",
++ "तेव्हा "
++ ],
++ "when": [
++ "* ",
++ "जेव्हा "
++ ]
+ }
+ }
+diff --git a/features/cmdline.lang_list.feature b/features/cmdline.lang_list.feature
+index f77e747..20822ec 100644
+--- a/features/cmdline.lang_list.feature
++++ b/features/cmdline.lang_list.feature
+@@ -39,21 +39,53 @@ Feature: Command-line options: Use behave --lang-list
+ fa: فارسی / Persian
+ fi: suomi / Finnish
+ fr: français / French
+- """
+- And the command output should contain:
+- """
+- sv: Svenska / Swedish
+- ta: தமிழ் / Tamil
+- th: ไทย / Thai
+- tl: తెలుగు / Telugu
+- tlh: tlhIngan / Klingon
+- tr: Türkçe / Turkish
+- tt: Татарча / Tatar
+- uk: Українська / Ukrainian
+- ur: اردو / Urdu
+- uz: Узбекча / Uzbek
+- vi: Tiếng Việt / Vietnamese
+- zh-CN: 简体中文 / Chinese simplified
+- zh-TW: 繁體中文 / Chinese traditional
++ ga: Gaeilge / Irish
++ gj: ગુજરાતી / Gujarati
++ gl: galego / Galician
++ he: עברית / Hebrew
++ hi: हिंदी / Hindi
++ hr: hrvatski / Croatian
++ ht: kreyòl / Creole
++ hu: magyar / Hungarian
++ id: Bahasa Indonesia / Indonesian
++ is: Íslenska / Icelandic
++ it: italiano / Italian
++ ja: 日本語 / Japanese
++ jv: Basa Jawa / Javanese
++ ka: ქართველი / Georgian
++ kn: ಕನ್ನಡ / Kannada
++ ko: 한국어 / Korean
++ lt: lietuvių kalba / Lithuanian
++ lu: Lëtzebuergesch / Luxemburgish
++ lv: latviešu / Latvian
++ mk-Cyrl: Македонски / Macedonian
++ mk-Latn: Makedonski (Latinica) / Macedonian (Latin)
++ mn: монгол / Mongolian
++ mr: मराठी / Marathi
++ ne: नेपाली / Nepali
++ nl: Nederlands / Dutch
++ no: norsk / Norwegian
++ pa: ਪੰਜਾਬੀ / Panjabi
++ pl: polski / Polish
++ pt: português / Portuguese
++ ro: română / Romanian
++ ru: русский / Russian
++ sk: Slovensky / Slovak
++ sl: Slovenski / Slovenian
++ sr-Cyrl: Српски / Serbian
++ sr-Latn: Srpski (Latinica) / Serbian (Latin)
++ sv: Svenska / Swedish
++ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
++ th: ไทย / Thai
++ tlh: tlhIngan / Klingon
++ tr: Türkçe / Turkish
++ tt: Татарча / Tatar
++ uk: Українська / Ukrainian
++ ur: اردو / Urdu
++ uz: Узбекча / Uzbek
++ vi: Tiếng Việt / Vietnamese
++ zh-CN: 简体中文 / Chinese simplified
++ zh-TW: 繁體中文 / Chinese traditional
+ """
+ But the command output should not contain "Traceback"
+diff --git a/issue.features/issue0309.feature b/issue.features/issue0309.feature
+index c8a8857..b50e32d 100644
+--- a/issue.features/issue0309.feature
++++ b/issue.features/issue0309.feature
+@@ -52,8 +52,8 @@ Feature: Issue #309 -- behave --lang-list fails on Python3
+ """
+ sv: Svenska / Swedish
+ ta: தமிழ் / Tamil
++ te: తెలుగు / Telugu
+ th: ไทย / Thai
+- tl: తెలుగు / Telugu
+ tlh: tlhIngan / Klingon
+ tr: Türkçe / Turkish
+ tt: Татарча / Tatar
diff --git a/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
new file mode 100644
index 000000000..77afa73ca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
@@ -0,0 +1,22 @@
+From 5947e49d48a1eb5eabef3e6f3af4001750a322bb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:40:58 +0100
+Subject: [PATCH] UPDATE CHANGES: Related to PR #895 and #827
+
+---
+ CHANGES.rst | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a3398d8..ff82132 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,6 +28,8 @@ ENHANCEMENTS:
+ * Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Support emojis in ``*.feature`` files and steps
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
++* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
++* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
+ * issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
new file mode 100644
index 000000000..68bb3320e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
@@ -0,0 +1,39 @@
+From b0ce168c776952e24209d71807727df3729e9021 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Tue, 12 Jan 2021 13:51:02 +0100
+Subject: [PATCH] FIX CI-TRAVIS: For python 2.7 builds (mock >= 4.0 only for
+ python.version >= 3.6)
+
+---
+ py.requirements/ci.travis.txt | 3 ++-
+ py.requirements/testing.txt | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index cbc60c0..1d6e050 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -6,7 +6,8 @@ pytest < 5.0; python_version < '3.0'
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index fc8fd82..9230c1f 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -8,7 +8,8 @@ pytest < 5.0; python_version < '3.0' # pytest >= 4.2
+ pytest >= 5.0; python_version >= '3.0'
+
+ pytest-html >= 1.19.0,<2.0
+-mock >= 2.0
++mock < 4.0; python_version < '3.6'
++mock >= 4.0; python_version >= '3.6'
+ PyHamcrest >= 2.0.2; python_version >= '3.0'
+ PyHamcrest < 2.0; python_version < '3.0'
+
diff --git a/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
new file mode 100644
index 000000000..63d48142e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
@@ -0,0 +1,126 @@
+From 17b6fcf8c77136d6c9ae29278f0b96bcc80575a9 Mon Sep 17 00:00:00 2001
+From: kingbuzzman <buzzi.javier@gmail.com>
+Date: Fri, 19 Jun 2020 09:18:51 -0400
+Subject: [PATCH] Adds the ability to use a custom runner in the behave command
+
+---
+ behave/__main__.py | 2 +-
+ behave/configuration.py | 16 ++++++++++++++++
+ docs/behave.rst | 5 +++++
+ tests/unit/test_configuration.py | 19 +++++++++++++++++++
+ 4 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/behave/__main__.py b/behave/__main__.py
+index 3cae36d..edb99c4 100644
+--- a/behave/__main__.py
++++ b/behave/__main__.py
+@@ -215,7 +215,7 @@ def main(args=None):
+ :return: 0, if successful. Non-zero, in case of errors/failures.
+ """
+ config = Configuration(args)
+- return run_behave(config)
++ return run_behave(config, runner_class=config.runner_class)
+
+
+ if __name__ == "__main__":
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 65e2e3e..04c014a 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -8,6 +8,7 @@ import re
+ import sys
+ import shlex
+ import six
++from importlib import import_module
+ from six.moves import configparser
+
+ from behave.model import ScenarioOutline
+@@ -65,6 +66,16 @@ class LogLevel(object):
+ # -----------------------------------------------------------------------------
+ # CONFIGURATION SCHEMA:
+ # -----------------------------------------------------------------------------
++
++def valid_python_module(path):
++ try:
++ module_path, class_name = path.rsplit('.', 1)
++ module = import_module(module_path)
++ return getattr(module, class_name)
++ except (ValueError, AttributeError, ImportError):
++ raise argparse.ArgumentTypeError("No module named '%s' was found." % path)
++
++
+ options = [
+ (("-c", "--no-color"),
+ dict(action="store_false", dest="color",
+@@ -111,6 +122,11 @@ options = [
+ dict(metavar="PATH", dest="junit_directory",
+ default="reports",
+ help="""Directory in which to store JUnit reports.""")),
++
++ (("--runner-class",),
++ dict(action="store",
++ default="behave.runner.Runner", type=valid_python_module,
++ help="Tells Behave to use a specific runner. (default: %(default)s)")),
+
+ ((), # -- CONFIGFILE only
+ dict(dest="default_format",
+diff --git a/docs/behave.rst b/docs/behave.rst
+index 25ce523..8c1c125 100644
+--- a/docs/behave.rst
++++ b/docs/behave.rst
+@@ -55,6 +55,11 @@ You may see the same information presented below at any time using ``behave
+
+ Directory in which to store JUnit reports.
+
++.. option:: --runner-class
++
++ This allows you to use your own custom runner. The default is
++ ``behave.runner.Runner``.
++
+ .. option:: -f, --format
+
+ Specify a formatter. If none is specified the default formatter is
+diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
+index c96cf63..025a6d0 100644
+--- a/tests/unit/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -5,6 +5,7 @@ import six
+ import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
++from behave.runner import Runner as BaseRunner
+ from unittest import TestCase
+
+
+@@ -37,6 +38,10 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
+
++class CustomTestRunner(BaseRunner):
++ """Custom, dummy runner"""
++
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -92,6 +97,20 @@ class TestConfiguration(object):
+ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
++ def test_settings_runner_class(self, capsys):
++ config = Configuration("")
++ assert BaseRunner == config.runner_class
++
++ def test_settings_runner_class_custom(self, capsys):
++ config = Configuration(["--runner-class=tests.unit.test_configuration.CustomTestRunner"])
++ assert CustomTestRunner == config.runner_class
++
++ def test_settings_runner_class_invalid(self, capsys):
++ with pytest.raises(SystemExit):
++ Configuration(["--runner-class=does.not.exist.Runner"])
++ captured = capsys.readouterr()
++ assert "No module named 'does.not.exist.Runner' was found." in captured.err
++
+
+ class TestConfigurationUserData(TestCase):
+ """Test userdata aspects in behave.configuration.Configuration class."""
diff --git a/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
new file mode 100644
index 000000000..bb941744b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
@@ -0,0 +1,59 @@
+From 71084ed9b3ebf60379251179f9bd5806c76f554c Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 07:29:38 -0700
+Subject: [PATCH] Allow forcing color with --color=always
+
+even if stdout is not a tty (e.g.: Jenkins)
+---
+ behave/configuration.py | 7 ++++---
+ behave/formatter/pretty.py | 12 +++++++++---
+ 2 files changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 04c014a..1b0bc2b 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -82,9 +82,10 @@ options = [
+ help="Disable the use of ANSI color escapes.")),
+
+ (("--color",),
+- dict(action="store_true", dest="color",
+- help="""Use ANSI color escapes. This is the default
+- behaviour. This switch is used to override a
++ dict(dest="color", choices=["never", "always", "auto"],
++ default="auto", const="auto", nargs="?",
++ help="""Use ANSI color escapes. Defaults to %(const)r.
++ This switch is used to override a
+ configuration file setting.""")),
+
+ (("-d", "--dry-run"),
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index 794e1d7..b97438a 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -66,9 +66,7 @@ class PrettyFormatter(Formatter):
+ super(PrettyFormatter, self).__init__(stream_opener, config)
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+- isatty = getattr(self.stream, "isatty", lambda: True)
+- stream_supports_colors = isatty()
+- self.monochrome = not config.color or not stream_supports_colors
++ self.monochrome = self._get_monochrome(config)
+ self.show_source = config.show_source
+ self.show_timings = config.show_timings
+ self.show_multiline = config.show_multiline
+@@ -83,6 +81,14 @@ class PrettyFormatter(Formatter):
+ self.indentations = []
+ self.step_lines = 0
+
++ def _get_monochrome(self, config):
++ isatty = getattr(self.stream, "isatty", lambda: True)
++ if config.color == 'always':
++ return False
++ elif config.color == 'never':
++ return True
++ else:
++ return not isatty()
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
new file mode 100644
index 000000000..18ae00b5f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
@@ -0,0 +1,43 @@
+From 166f6a1236a2627a5c30f8dfffe7ddf7bad956be Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 09:11:22 -0700
+Subject: [PATCH] Allow --color with no value followed by posarg
+
+Allow commands like `--color features/whizbang.feature` to work
+Without this, argparse will treat the positional arg as the value to
+--color and we'd get:
+ argument --color: invalid choice: 'features/whizbang.feature'
+---
+ behave/configuration.py | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 1b0bc2b..0fdfd5e 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default="auto", const="auto", nargs="?",
++ default=None, const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -562,6 +562,16 @@ class Configuration(object):
+ # -- AUTO-DISCOVER: Verbose mode from command-line args.
+ verbose = ("-v" in command_args) or ("--verbose" in command_args)
+
++ # Allow commands like `--color features/whizbang.feature` to work
++ # Without this, argparse will treat the positional arg as the value to
++ # --color and we'd get:
++ # argument --color: invalid choice: 'features/whizbang.feature'
++ # (choose from 'never', 'always', 'auto')
++ if '--color' in command_args:
++ color_arg_pos = command_args.index('--color')
++ if os.path.exists(command_args[color_arg_pos + 1]):
++ command_args.insert(color_arg_pos + 1, '--')
++
+ self.version = None
+ self.tags_help = None
+ self.lang_list = None
diff --git a/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
new file mode 100644
index 000000000..1ec5b7531
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
@@ -0,0 +1,31 @@
+From 27861b5be77f6d9bbade565a15aaaaab18a1f4a2 Mon Sep 17 00:00:00 2001
+From: Marc Abramowitz <abramowi@adobe.com>
+Date: Thu, 3 Aug 2017 13:29:55 -0700
+Subject: [PATCH] Add BEHAVE_COLOR env var
+
+---
+ behave/configuration.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 0fdfd5e..e7d385d 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -83,7 +83,7 @@ options = [
+
+ (("--color",),
+ dict(dest="color", choices=["never", "always", "auto"],
+- default=None, const="auto", nargs="?",
++ default=os.getenv('BEHAVE_COLOR'), const="auto", nargs="?",
+ help="""Use ANSI color escapes. Defaults to %(const)r.
+ This switch is used to override a
+ configuration file setting.""")),
+@@ -507,7 +507,7 @@ class Configuration(object):
+ """Configuration object for behave and behave runners."""
+ # pylint: disable=too-many-instance-attributes
+ defaults = dict(
+- color=sys.platform != "win32",
++ color='never' if sys.platform == "win32" else os.getenv('BEHAVE_COLOR', 'auto'),
+ show_snippets=True,
+ show_skipped=True,
+ dry_run=False,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
new file mode 100644
index 000000000..c4bebf203
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
@@ -0,0 +1,33 @@
+From f6e84e826857c13b583555abb7b53fc993c4b768 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Dom=C3=ADnguez?=
+ <pablo.dominguezsantana@telefonica.com>
+Date: Thu, 9 Sep 2021 12:19:21 +0200
+Subject: [PATCH] fix: malformed table rows warning
+
+---
+ behave/parser.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/behave/parser.py b/behave/parser.py
+index 58c68be..b71adfe 100644
+--- a/behave/parser.py
++++ b/behave/parser.py
+@@ -41,6 +41,7 @@ Keyword aliases:
+ # pylint: enable=line-too-long
+
+ from __future__ import absolute_import, with_statement
++import logging
+ import re
+ import sys
+ import six
+@@ -644,6 +645,10 @@ class Parser(object):
+ self.state = "steps"
+ return self.action_steps(line)
+
++ if not re.match(r"^(|.+)\|$", line):
++ logger = logging.getLogger("behave")
++ logger.warning(u"Malformed table row at %s: line %i", self.feature.filename, self.line)
++
+ # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values.
+ # Search for pipe(s) that are not preceeded with an escape char.
+ cells = [cell.replace("\\|", "|").strip()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
new file mode 100644
index 000000000..11ff79ac5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
@@ -0,0 +1,42 @@
+From a45c062b3d792aed5fae393ca763b5399b501e00 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 12 Sep 2021 16:07:32 +0200
+Subject: [PATCH] FIX #955: setup: Remove attribute 'use_2to3'
+
+REASON:
+* This attribute is deprecated since setuptools >= v58.0.2 (2021-09-06).
+* 2to3 conversion should not be needed anymore.
+ Currently, code should run on python2 and python3 (by using six, etc.).
+---
+ setup.py | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index fd89bda..ba407fd 100644
+--- a/setup.py
++++ b/setup.py
+@@ -118,8 +118,8 @@ setup(
+ "pylint",
+ ],
+ },
+- # MAYBE-DISABLE: use_2to3
+- use_2to3= bool(python_version >= 3.0),
++ # DISABLED: use_2to3= bool(python_version >= 3.0),
++ # DEPRECATED SINCE: setuptools v58.0.2 (2021-09-06)
+ license="BSD",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+@@ -129,12 +129,11 @@ setup(
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+- "Programming Language :: Python :: 3.3",
+- "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
++ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
index 000000000..1f66cb035
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
@@ -0,0 +1,21 @@
+From b158de954ca1cc760ba4833ad4a9e386650723ae Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 20 Sep 2021 16:13:59 +0200
+Subject: [PATCH] Add info for fixed issue #955
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index ff82132..880fd91 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -42,6 +42,7 @@ FIXED:
+
+ * FIXED: Some tests related to python3.9
+ * FIXED: active-tag logic if multiple tags with same category exists.
++* issue #955: setup: Remove attribute 'use_2to3' (submitted by: krisgesling)
+ * issue #772: ScenarioOutline.Examples without table (submitted by: The-QA-Geek)
+ * issue #755: Failures with Python 3.8 (submitted by: hroncok)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
diff --git a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
index 1dcc7d218..745d1e2b2 100644
--- a/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
+++ b/meta-python/recipes-devtools/python/python3-behave_1.2.6.bb
@@ -4,8 +4,131 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=d950439e8ea6ed233e4288f5e1a49c06"
PV .= "+git${SRCREV}"
-SRCREV = "9520119376046aeff73804b5f1ea05d87a63f370"
-SRC_URI += "git://github.com/behave/behave;branch=master;protocol=https"
+SRCREV = "c0f3faf47ff05f549ec049599eb2f24069b0e51e"
+SRC_URI += "file://0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch \
+ file://0002-UPDATE-FIXED-725.patch \
+ file://0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch \
+ file://0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch \
+ file://0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch \
+ file://0006-Formatter-Add-basic-support-output-for-Rules.patch \
+ file://0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch \
+ file://0008-Correct-examples-and-docstring.patch \
+ file://0009-FIX-feature.run_items-processing-with-Rule-s.patch \
+ file://0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch \
+ file://0011-Cleanup-Dependent-package-versions-in-requirements.patch \
+ file://0012-docs-conf.py-tweaks.patch \
+ file://0013-FIX-Misspelled-after_rule-hook-was-after_after.patch \
+ file://0014-Add-hints-on-Gherkin-v6-grammar-issues.patch \
+ file://0015-README-ReST-tweaks.patch \
+ file://0016-Example-using-Gherkin-v6-grammar.patch \
+ file://0017-PREPARE-Python-3.8-support.patch \
+ file://0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch \
+ file://0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch \
+ file://0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch \
+ file://0021-FIX-py3.8-logging.Formatter.validate-problem.patch \
+ file://0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch \
+ file://0023-UPDATE-Add-755-info.patch \
+ file://0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch \
+ file://0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch \
+ file://0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch \
+ file://0027-Comment-tweaks.patch \
+ file://0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch \
+ file://0029-Steps-catalog-should-not-break-configured-rerun-sett.patch \
+ file://0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch \
+ file://0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch \
+ file://0032-Add-info-on-merged-pull-588.patch \
+ file://0033-Tweak-tests-required-by-pytest-5.0.patch \
+ file://0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch \
+ file://0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch \
+ file://0036-FIX-Remove-test-from-pytest-run.patch \
+ file://0037-Select-by-location-Add-support-for-Scenario-containe.patch \
+ file://0038-docs-Add-description-for-Select-by-location-for-Scen.patch \
+ file://0039-tests-Fix-warnings-for-python3.patch \
+ file://0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch \
+ file://0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch \
+ file://0042-FIX-Invalid-escape-char-in-regex-w-python3.patch \
+ file://0043-Example-related-to-question-in-756.patch \
+ file://0044-FIX-python3.8-regressions-on-CI-server.patch \
+ file://0045-UPDATE-Mark-issue-755-as-fixed.patch \
+ file://0046-UPDATE-Cucumber-gherkin-languages.json.patch \
+ file://0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch \
+ file://0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch \
+ file://0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch \
+ file://0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch \
+ file://0051-Improve-support-for-feature.background-inheritance-f.patch \
+ file://0052-Add-support-for-runtime-constraints.patch \
+ file://0053-Use-runtime-constraints.patch \
+ file://0054-CLEANUP-Remove-deprecated-parts.patch \
+ file://0055-CLEANUP-Remove-deprecated-parts.patch \
+ file://0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch \
+ file://0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch \
+ file://0058-UTIL-Formatting-tweaks.patch \
+ file://0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch \
+ file://0060-Added-issue-unit-test.patch \
+ file://0061-Merge-pull-request-767-with-minor-tweaks.patch \
+ file://0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch \
+ file://0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch \
+ file://0065-Nibble-TravisCI-to-wake-up.patch \
+ file://0066-Tweak-pytest-version-selection.patch \
+ file://0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch \
+ file://0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch \
+ file://0069-UPDATE-dependencies-path.py-path-pytest.patch \
+ file://0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch \
+ file://0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch \
+ file://0072-Cleanup-comments.patch \
+ file://0073-FIX-sphinx-build-problem-async_steps3x.py.patch \
+ file://0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch \
+ file://0075-docs-parse_expression-add-links-to-parse_type-module.patch \
+ file://0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch \
+ file://0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch \
+ file://0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch \
+ file://0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch \
+ file://0080-DOCS-Update-API-description-for-Runner-Operation.patch \
+ file://0081-FIX-DOCS-Runner-operations-typo.patch \
+ file://0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch \
+ file://0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch \
+ file://0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch \
+ file://0085-Add-renovate.json.patch \
+ file://0086-PRPEPARE-RENOVATE-With-adaptions.patch \
+ file://0087-Pin-dependencies.patch \
+ file://0088-renovate-Extend-pip-requirements-file-list.patch \
+ file://0089-PIN-REQUIREMENTS-Extend-to-all-places.patch \
+ file://0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch \
+ file://0091-Docs-change-code-blocks-from-bash-to-console.patch \
+ file://0092-Fix-typo-in-tutorial.patch \
+ file://0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch \
+ file://0094-UPDATE-PR-877-was-merged.patch \
+ file://0095-capitalizing-steps.patch \
+ file://0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch \
+ file://0097-Test-against-PowerPC-CPU-support-Travis-867.patch \
+ file://0098-Add-Context.attach-docs-and-test.patch \
+ file://0099-Add-Contributing-chapter.patch \
+ file://0100-Adapt-Tox-target-for-building-the-docs.patch \
+ file://0101-Mention-HTML-formatter-in-documentation.patch \
+ file://0102-Use-console-highlighting-for-pip-install-docs.patch \
+ file://0103-docs-fix-simple-typo-tuorial-tutorial.patch \
+ file://0104-Add-support-for-python3.9-by-using-active-tags.patch \
+ file://0105-PREFER-python3-from-now-on.patch \
+ file://0106-REMOVE-invoke-scripts.patch \
+ file://0107-FIX-Deprecated-warnings-for-Python-3.x.patch \
+ file://0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch \
+ file://0109-FIX-Active-tag-logic.patch \
+ file://0110-FIX-Tests-w-more.features.patch \
+ file://0111-FIX-Some-regressions-with-Python-3.9.patch \
+ file://0112-docs-Update-new-and-noteworthy.patch \
+ file://0113-Add-diagnostic-helper-function-to-print-the-current-.patch \
+ file://0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch \
+ file://0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch \
+ file://0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch \
+ file://0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch \
+ file://0118-Allow-forcing-color-with-color-always.patch \
+ file://0119-Allow-color-with-no-value-followed-by-posarg.patch \
+ file://0120-Add-BEHAVE_COLOR-env-var.patch \
+ file://0121-fix-malformed-table-rows-warning.patch \
+ file://0122-FIX-955-setup-Remove-attribute-use_2to3.patch \
+ file://0123-Add-info-for-fixed-issue-955.patch \
+ "
S = "${WORKDIR}/git"
@@ -16,3 +139,5 @@ RDEPENDS:${PN} += " \
${PYTHON_PN}-setuptools \
${PYTHON_PN}-six \
"
+
+PV = "6"
--
2.39.2
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2023-11-04 1:38 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-12 5:30 [AUH] python3-behave: upgrading to 6 FAILED auh
-- strict thread matches above, loose matches on Subject: below --
2023-07-31 5:08 auh
2023-08-05 7:38 auh
2023-11-04 1:38 auh
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.